How it works...
In step 5, we defined the swiping property on the state. This property is just a
Boolean that will be set to true when the dragging starts and to false when it has
completed. We need this information in order to lock the vertical scrolling on the
list while dragging around the item.
In step 7, we defined the content of each row in the list. The onDragStart property
receives the handleToggleSwipe method, which will be executed when the dragging
starts. We are also going to execute the same method when the dragging is
completed.
In the same step, we also send the handleRemoveContact method to each item. As the
name suggests, we are going to remove the current item from the list when the
user swipes it out.
In step 11, we defined defaultProps and state for the item component. In past
recipes, we have been creating animations using a single value, but for this case
we need to handle the x and y coordinates, so we'll need an instance
of Animated.ValueXY. Internally, this class handles two Animated.Value instances, and
therefore the API is almost identical to those we've seen before.
In step 12, PanResponder gets created. The gesture system in React Native, like the
event system in the browser, handles gestures in two phases when there's a touch
event: the capture and the bubble. In our case, we need to use the capture phase
to figure out whether the current event is pressing the item or whether it's trying
to drag it. onMoveShouldSetPanResponderCapture will capture the event. Then, we need
to decide whether we'll drag the item or not by returning true or false.
The onPanResponderMove prop will get the values from the animation on each frame,
which will be applied to the pan object in the state. We need to
use Animated.event to access the animation values for each frame. In this case, we
only need the x value. Later, we'll use this value to run a different animation
while returning the element to its original place or removing it from the screen.
The onPanResponderRelease function will be executed when the user releases the
item. If, for any other reason, the dragging gets
interrupted, onPanResponderTerminate will get executed instead.
In step 13, we need to check whether the current event is a simple press or a
dragging. We can do this by checking the delta on the x-axis. If the touch event
has been moved more than two pixels, then the user is trying to drag the item,
otherwise, they're trying to press the button. We evaluate the difference as an
absolute number because the movement could be from left to right or right to
left, and we want to accommodate both movements.
In step 14, we need to get the distance the item has moved with respect to the
width of the device. If this distance is below our threshold we defined
in setThreshold, then we need to remove these items. We are defining
the config object for each animation, which will otherwise return the item to the
original position. But if we need to remove the item, we check the direction and
set the configuration accordingly.
In step 16, we defined the JSX. We set the styles that we want to animate
on Animated.View. In this case, it's the left property, but instead of manually
creating an object, we can call the getLayout method from our instance
of Animated.ValueXY that we stored in state.pan, which returns the top and left
properties with their existing values.
In the same step, we also set the event handlers for Animated.View by spreading
out this.panResponder.panHandlers with a spread operator, which binds the dragging
configuration we defined in the previous steps to Animated.View.
We also defined a call to the onPress callback from props, passing in the
current contact information.
See also
You can find the PanResponder API documentation at:
https://facebook.github.io/react-native/docs/panresponder.html
Creating a Facebook reactions widget
In this recipe, we'll be creating a component that emulates the Facebook reaction
widget. We will have a like button image which, when pressed, will show five
icons. The row of icons will use a staggered slide-in animation while increasing
opacity from 0 to 1.
Getting ready
Let's create an empty app called facebook-widget.
We are going to need some images to display a fake timeline. A few pictures of
your cat will work, or you can use the cat pictures included in the corresponding
repository on GitHub (https://github.com/warlyware/react-native-cookbook/tree/master/ch
apter-7/facebook-widget). We'll also need five icons to display the five reactions,
such as, angry, laughing, heart, and surprised, which can also be found in the
corresponding repository.
To start we'll create two JavaScript files in our empty
app: Reactions/index.js and Reactions/Icon.js. We need to copy our cat pictures to
an images/ folder in the root of the app, and the reaction icons should be placed
in Reactions/images.
How to do it...
1. We are going to be creating a fake Facebook timeline on the App class. Let's
start by importing the dependencies, as follows:
import React from 'react';
import {
Dimensions,
Image,
Text,
ScrollView,
StyleSheet,
SafeAreaView,
} from 'react-native';
import Reactions from './Reactions';
2. We'll need to import some images to render in our timeline. The JSX in this
step is very simple: it's just a toolbar, a ScrollView with two Image, and
two Reaction components, as follows:
const image1 = require('./images/01.jpg');
const image2 = require('./images/02.jpg');
const { width } = Dimensions.get('window');
const App = () => (
<SafeAreaView style={styles.main}>
<Text style={styles.toolbar}>Reactions</Text>
<ScrollView style={styles.content}>
<Image source={image1} style={styles.image} resizeMode="cover" />
<Reactions />
<Image source={image2} style={styles.image} resizeMode="cover" />
<Reactions />
</ScrollView>
</SafeAreaView>
);
export default App;
3. We need to add some basic styles for this component, as follows:
const styles = StyleSheet.create({
main: {
flex: 1,
},
toolbar: {
backgroundColor: '#3498db',
color: '#fff',
fontSize: 22,
padding: 20,
textAlign: 'center',
},
content: {
flex: 1,
},
image: {
width,
height: 300,
},
});
4. We are ready to start working on the Reactions component of this recipe.
Let's start by importing dependencies, as follows. We will build out the
imported Icon component in later steps:
import React, { Component } from 'react';
import {
Image,
Text,
TouchableOpacity,
StyleSheet,
View,
} from 'react-native';
import Icon from './Icon';
5. Let's define defaultProps and the initial state next. We'll also need to require
the like icon image to display it on screen, as follows:
const image = require('./images/like.png');
export default class Reactions extends Component {
static defaultProps = {
icons: [
'like', 'heart', 'angry', 'laughing', 'surprised',
],
};
state = {
show: false,
selected: '',
};
// Defined at later steps
}
6. Let's define two methods: one that sets the selected value of state to the
selected reaction, and another that toggles the show value of state to show or
hide the row of reactions accordingly, as follows:
onSelectReaction = (reaction) => {
this.setState({
selected: reaction,
});
this.toggleReactions();
}
toggleReactions = () => {
this.setState({
show: !this.state.show,
});
};
7. We'll define the render method for this component. We are going to display
an image, which when pressed, will call the toggleReactions method that we
defined previously, as follows:
render() {
const { style } = this.props;
const { selected } = this.state;
return (
<View style={[style, styles.container]}>
<TouchableOpacity onPress={this.toggleReactions}>
<Image source={image} style={styles.icon} />
</TouchableOpacity>
<Text>{selected}</Text>
{this.renderReactions()}
</View>
);
}
8. You'll notice in this step that we're calling the renderReactions method. Next,
we'll render all of the icons that we want to display when the user presses
the main reaction button, as follows:
renderReactions() {
const { icons } = this.props;
if (this.state.show) {
return (
<View style={styles.reactions}>
{ icons.map((name, index) => (
<Icon
key={index}
name={name}
delay={index * 100}
index={index}
onPress={this.onSelectReaction}
/>
))
}
</View>
);
}
}
9. We need to se styles for this component. We'll set sizes for the reaction icon
images and define some padding. The reactions container will have a height
of 0, since the icons will be floating, and we don't want any extra space
added, as follows:
const styles = StyleSheet.create({
container: {
padding: 10,
},
icon: {
width: 30,
height: 30,
},
reactions: {
flexDirection: 'row',
height: 0,
},
});
10. The Icon component is currently missing, so if we try to run our app at this
point, it will fail. Let's build out this component by opening the
Reactions/Icon.js file and adding the imports for the component, as follows:
import React, { Component } from 'react';
import {
Animated,
Dimensions,
Easing,
Image,
StyleSheet,
TouchableOpacity,
View,
} from 'react-native';
11. Let's define the icons we'll be using. We are going to use an object for the
icons so that we can easily retrieve each image by its key name, as follows:
const icons = {
angry: require('./images/angry.png'),
heart: require('./images/heart.png'),
laughing: require('./images/laughing.png'),
like: require('./images/like.png'),
surprised: require('./images/surprised.png'),
};
12. Now we should define defaultProps for this component. We don't need to
define an initial state:
export default class Icon extends Component {
static defaultProps = {
delay: 0,
onPress: () => {},
};
}
13. The icons should appear on screen via an animation, so we'll need to create
and run the animation when the component is mounted, as follows:
componentWillMount() {
this.animatedValue = new Animated.Value(0);
}
componentDidMount() {
const { delay } = this.props;
Animated.timing(
this.animatedValue,
{
toValue: 1,
duration: 200,
easing: Easing.elastic(1),
delay,
}
).start();
}
14. When the icon is pressed, we need to execute the onPress callback to inform
the parent that a reaction was selected. We will send the name of the
reaction as a parameter, as follows:
onPressIcon = () => {
const { onPress, name } = this.props;
onPress(name);
}
15. The last piece of the puzzle is the render method, where we'll define the JSX
for this component, as follows:
render() {
const { name, index, onPress } = this.props;
const left = index * 50;
const top = this.animatedValue.interpolate({
inputRange: [0, 1],
outputRange: [10, -95],
});
const opacity = this.animatedValue;
return (
<Animated.View
style={[
styles.icon,
{ top, left, opacity },
]}
>
<TouchableOpacity onPress={this.onPressIcon}>
<Image source={icons[name]} style={styles.image} />
</TouchableOpacity>
</Animated.View>
);
}
16. As the final step, we'll add styles for each icon. We need the icons to float,
so we'll set position to absolute and width and height to 40 pixels. After this
change, we should be able to run our app:
icon: {
position: 'absolute',
},
image: {
width: 40,
height: 40,
},
});
17. The final app should look something like this screenshot:
How it works...
In step 2, we defined the Reactions component in the timeline. For now, we are not
focusing on handling data, but rather on displaying the UI. Therefore, we are not
sending any callback via Reactions props to get the selected value.
In step 5, we defined defaultProps and the initial state.
We have two properties in the state:
The show prop is a Boolean. We use it to toggle the reactions icons when the
user presses the main button. When false, we hide the reactions, and
when true, we run the animation to show each icon.
selected contains the current selection. Every time a new reaction gets
selected, we are going to update this prop.
In step 8, we render the icons. Here, we need to send the name of the icon to
every instance created. We also send a delay of 100 milliseconds for each icon,
which will create a nice stagger animation. The onPress prop receives
the onSelectReaction method defined in step 6, which sets the selected reaction
on state.
In step 13, we create the animation. First, we define the animatedValue variable
using the Animated.Value helper, which, as mentioned in previous recipes, is the
class responsible for holding the value for each frame in the animation. As soon
as the component is mounted, we run the animation. The animations progress
from 0 to 1, with a duration of 200 milliseconds and using an elastic easing
function, and we delay the animation based on the received delay prop.
In step 15, we defined the JSX for the Icon component. Here we animate
the top and opacity properties. For the top property, we need to interpolate the
values from animatedValue, so that the icon moves 95 pixels up from its original
position. The required values for the opacity property are from 0 to 1, and since
we don't need to interpolate anything to accomplish this, we can
use animatedValue directly.
The left value is calculated based on the index: we just move the icon 50 pixels to
the left of the previous icon, which will avoid rendering the icons all in the
sample place.
Displaying images in fullscreen
In this recipe, we'll create a timeline of images. When the user presses any of the
images, it will fullscreen the image with a black background.
We will use an opacity animation for the background, and we'll slide the image
in from its original position.
Getting ready
Let's create an empty app called photo-viewer.
In addition, we'll also create PostContainer/index.js for showing each image in the
timeline, and PhotoViewer/index.js for showing the selected image in fullscreen.
You can either use the images included in this recipe's repository hosted on
GitHub (https://github.com/warlyware/react-native-cookbook/tree/master/chapter-7/photo-v
iewer), or use a few photos of your own. Place them in an images folder in the root
of the project.
How to do it...
1. We are going to display a timeline with images in the App class. Let's import
all of the dependencies, including the two other components we'll build out
in later steps, as follows:
import React, { Component } from 'react';
import {
Dimensions,
Image,
Text,
ScrollView,
StyleSheet,
SafeAreaView,
} from 'react-native';
import PostContainer from './PostContainer';
import PhotoViewer from './PhotoViewer';
2. In this step, we'll define the data that we are going to render. It's just a
simple array of objects containing title and image, as follows:
const image1 = require('./images/01.jpg');
const image2 = require('./images/02.jpg');
const image3 = require('./images/03.jpg');
const image4 = require('./images/04.jpg');
const timeline = [
{ title: 'Enjoying the fireworks', image: image1 },
{ title: 'Climbing the Mount Fuji', image: image2 },
{ title: 'Check my last picture', image: image3 },
{ title: 'Sakuras are beautiful!', image: image4 },
];
3. Now we need to declare the initial state of this component. We will update
the selected and position properties when any of the images gets pressed, as
follows:
export default class App extends Component {
state = {
selected: null,
position: null,
};
// Defined in following steps
}
4. In order to update state, we are going to declare two methods: one to set the
value of the image that has been pressed and another to remove those
values when the viewer gets closed:
showImage = (selected, position) => {
this.setState({
selected,
position,
});
}
closeViewer = () => {
this.setState({
selected: null,
position: null,
});
}
5. Now we are ready to work on the render method. Here we'll need to render
each image inside ScrollView so the list will be scrollable, as follows:
render() {
return (
<SafeAreaView style={styles.main}>
<Text style={styles.toolbar}>Timeline</Text>
<ScrollView style={styles.content}>
{
timeline.map((post, index) =>
<PostContainer key={index} post={post}
onPress={this.showImage} />
)
}
</ScrollView>
{this.renderViewer()}
</SafeAreaView>
);
}
6. In the previous step, we are calling the renderViewer method. Here we'll show
the viewer component only if there's a post selected in the state. We are also
sending the initial position to start the animation and a callback to close the
viewer, as follows:
renderViewer() {
const { selected, position } = this.state;
if (selected) {
return (
<PhotoViewer
post={selected}
position={position}
onClose={this.closeViewer}
/>
);
}
}
7. The styles for this component are very simple, only some colors and
padding, as follows:
const styles = StyleSheet.create({
main: {
backgroundColor: '#ecf0f1',
flex: 1,
},
toolbar: {
backgroundColor: '#2c3e50',
color: '#fff',
fontSize: 22,
padding: 20,
textAlign: 'center',
},
content: {
flex: 1,
},
});
8. The timeline is complete, but if we try to run our app, it will fail. Let's work
on the PostContainer component. We'll start by importing the dependencies, as
follows:
import React, { Component } from 'react';
import {
Dimensions,
Image,
Text,
TouchableOpacity,
StyleSheet,
View,
} from 'react-native';
9. We only need two props for this component. The post prop will receive the
image data, title and image, and the onPress prop is a callback that we'll
execute when the image gets pressed, as follows:
const { width } = Dimensions.get('window');
export default class PostContainer extends Component {
static defaultProps = {
onPress: ()=> {},
};
// Defined on following steps
}
10. This component will be inside of ScrollView. This means its position will be
changing when the user starts scrolling the content. When pressing the
image, we need to get the current position on the screen and send this
information to the parent component, as follows:
onPressImage = (event) => {
const { onPress, post } = this.props;
this.refs.main.measure((fx, fy, width, height, pageX, pageY) => {
onPress(post, {
width,
height,
pageX,
pageY,
});
});
}
11. It's time to define the JSX for this component. To keep things simple, we
are only going to render image and title:
render() {
const { post: { image, title } } = this.props;
return (
<View style={styles.main} ref="main">
<TouchableOpacity
onPress={this.onPressImage}
activeOpacity={0.9}
>
<Image
source={image}
style={styles.image}
resizeMode="cover"
/>
</TouchableOpacity>
<Text style={styles.title}>{title}</Text>
</View>
);
}
12. As always, we need to define some styles for this component. We are going
to add some colors and padding, as follows:
const styles = StyleSheet.create({
main: {
backgroundColor: '#fff',
marginBottom: 30,
paddingBottom: 10,
},
content: {
flex: 1,
},
image: {
width,
height: 300,
},
title: {
margin: 10,
color: '#ccc',
}
});
13. If we run the app now, we should be able to see the timeline, however if we
press any of the images, an error will be thrown. We need to define the
viewer, so let's open the PhotoViewer/index.js file and import the
dependencies:
import React, { Component } from 'react';
import {
Animated,
Dimensions,
Easing,
Text,
TouchableOpacity,
StyleSheet,
} from 'react-native';
14. Let's define props for this component. In order to center the image on the
screen, we need to know the height of the current device:
const { width, height } = Dimensions.get('window');
export default class PhotoViewer extends Component {
static defaultProps = {
onClose: () => {},
};
// Defined on following steps
}
15. We want to run two animations when showing this component, so we'll
need to initialize and run the animation after the component is mounted.
The animation is simple: it just goes from 0 to 1 in 400 milliseconds with
some easing applied, as follows:
componentWillMount() {
this.animatedValue = new Animated.Value(0);
}
componentDidMount() {
Animated.timing(
this.animatedValue,
{
toValue: 1,
duration: 400,
easing: Easing.in,
}
).start();
}
16. When the user presses the close button, we need to execute
the onClose callback to inform the parent that this component needs to be
removed, as follows:
onPressBtn = () => {
this.props.onClose();
}
17. We are going to split the render method into two steps. First, we need to
interpolate the values for the animations, as follows:
render() {
const { post: { image, title }, position } = this.props;
const top = this.animatedValue.interpolate({
inputRange: [0, 1],
outputRange: [position.pageY, height/2 - position.height/2],
});
const opacity = this.animatedValue;
// Defined on next step
}
18. We only need to define three elements: View to animate the
background, image to display the image, and a close button. We are setting
the opacity style to the main view, which will animate the image background
from transparent to black. The image will slide in at the same time, creating
a nice effect, as follows:
// Defined on previous step
render() {
return (
<Animated.View
style={[
styles.main,
{ opacity },
]}
>
<Animated.Image
source={image}
style={[
styles.image,
{ top, opacity }
]}
/>
<TouchableOpacity style={styles.closeBtn}
onPress={this.onPressBtn}
>
<Text style={styles.closeBtnText}>X</Text>
</TouchableOpacity>
</Animated.View>
);
}
19. We are almost done! The last step in this recipe is to define the styles. We
need to set the position of the main container to absolute so that the image
is on top of everything else. We'll also move the close button to the top-right
of the screen, as follows:
const styles = StyleSheet.create({
main: {
backgroundColor: '#000',
bottom: 0,
left: 0,
position: 'absolute',
right: 0,
top: 0,
},
image: {
width,
height: 300,
},
closeBtn: {
position: 'absolute',
top: 50,
right: 20,
},
closeBtnText: {
fontSize: 20,
color: '#fff',
fontWeight: 'bold',
},
});
20. The final app should look similar to the following screenshot:
How it works...
In step 4, we defined two properties on state: selected and position.
The selected property holds the image data for the pressed image, which can be
any of the timeline objects defined in step 3. The position property will hold the
current y-coordinate on the screen, which is used later to animate the image from
its original position to the center of the screen.
In step 5, we map over the timeline array to render each post. We used
the PostContainer element for each post, sending the post information and using
the onPress callback to set the pressed image.
In step 10, we need the current position of the image. To achieve this, we use
the measure method from the component we want to get the information from.
This method receives a callback function and retrieves, among other
properties, width, height, and the current position on the screen.
We are using a reference to access the component, declared in the JSX on the
next step.
In step 11, we declared the JSX for the component. In the main wrapper
container, we set the ref property, which is used to get the current position of the
image. Whenever we want to access a component on any of the methods of the
current class, we use a reference. We can create references by simply setting
the ref property and assigning a name to any component.
In step 18, we interpolate the animation values to get the correct top value for
each frame. The output of that interpolation will start from the current position
of the image and progress to the middle of the screen. This way, depending on
whether the values are negative or positive, the animation will run from bottom
to top, or the other way around.
We don't need to interpolate opacity, since the current animated value already
goes from 0 to 1.
See also
An in depth explanation of Refs and the DOM can be found at the following
link:
https://reactjs.org/docs/refs-and-the-dom.html.
Working with Application Logic and
Data
In this chapter, we will cover the following recipes:
Storing and retrieving data locally
Retrieving data from a remote API
Sending data to a remote API
Establishing real-time communication with WebSockets
Integrating persistent database functionality with Realm
Masking the application upon network connection loss
Synchronizing locally persisted data with a remote API
Introduction
One of the most important aspects of developing any application is handling
data. This data may come locally from the user, may be served by a remote
server that exposes an API, or, as with most business applications, may be some
combination of both. You may be wondering what strategies are best for dealing
with data, or how to even accomplish simple tasks such as making an HTTP
request. Luckily, React Native makes your life that much simpler by providing
mechanisms for easily dealing with data.
The open source community has taken things a step further and provided some
excellent modules that can be used with React Native. In this chapter, we will
discuss how to work with data in all aspects, and how it integrates into our React
Native applications.
Storing and retrieving data locally
When developing a mobile app, we need to consider the network challenges that
need to be overcome. A well-designed app should allow the user to continue
using the app when there is no internet connection. This requires the app to save
data locally on the device when there's no internet connection, and to also sync
that data with the server when the network is available again.
Another challenge to overcome is network connectivity, which might be slow or
limited. To improve the performance of our app, we should save critical data on
the local device to avoid putting stress on our server API.
In this recipe, we will learn about a basic and effective strategy for saving and
retrieving data locally from the device. We will create a simple app with a text
input and two buttons, one to save the content, of the field and one to load the
existing content. We will use the AsyncStorage class to achieve our goal.
Getting ready
We need to create an empty app named local-data-storage.
How to do it...
1. We'll begin with the App component. Let's start by importing all of the
dependencies:
import React, { Component } from 'react';
import {
Alert,
AsyncStorage,
StyleSheet,
Text,
TextInput,
TouchableOpacity,
View,
} from 'react-native';
2. Now, let's create the App class. We are going to create a key constant so that
we can set the name of the key we will use to save the content. On the state,
we'll have two properties: one to keep the value from the text input
component, and another to load and display the currently stored value:
const key = '@MyApp:key';
export default class App extends Component {
state = {
text: '',
storedValue: '',
};
//Defined in later steps
}
3. When the component mounts, we want to load the existing stored value – if
it exists. We'll display the content once the app loads, so we'll need to read
the local value in the componentWillMount life cycle method:
componentWillMount() {
this.onLoad();
}
4. The onLoad function loads the current content from the local storage. Like
localStorage in the browser, it's as easy as using the key we defined when
saving the data:
onLoad = async () => {
try {
const storedValue = await AsyncStorage.getItem(key);
this.setState({ storedValue });
} catch (error) {
Alert.alert('Error', 'There was an error while loading the
data');
}
}
5. Saving the data is straightforward as well. We'll declare a key to save any
data we want to associate with that key, via the setItem method
of AsyncStorage:
onSave = async () => {
const { text } = this.state;
try {
await AsyncStorage.setItem(key, text);
Alert.alert('Saved', 'Successfully saved on device');
} catch (error) {
Alert.alert('Error', 'There was an error while saving the
data');
}
}
6. Next, we need a function for saving the value from the input text to the
state. When the value of the input changes, we will get the new value and
save it to the state:
onChange = (text) => {
this.setState({ text });
}
7. Our UI will be simple: just a Text element to render the saved content, a
TextInput component to allow the user to enter a new value, and two buttons.
One button will call the onLoad function to load the current saved value, and
the other will save the value from the text input:
render() {
const { storedValue, text } = this.state;
return (
<View style={styles.container}>
<Text style={styles.preview}>{storedValue}</Text>
<View>
<TextInput
style={styles.input}
onChangeText={this.onChange}
value={text}
placeholder="Type something here..."
/>
<TouchableOpacity onPress={this.onSave} style=
{styles.button}>
<Text>Save locally</Text>
</TouchableOpacity>
<TouchableOpacity onPress={this.onLoad} style=
{styles.button}>
<Text>Load data</Text>
</TouchableOpacity>
</View>
</View>
);
}
8. Finally, let's add some styles. This will be simple colors, paddings, margins,
and a layout, as covered in Chapter 2, Creating a Simple React Native App:
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#fff',
},
preview: {
backgroundColor: '#bdc3c7',
width: 300,
height: 80,
padding: 10,
borderRadius: 5,
color: '#333',
marginBottom: 50,
},
input: {
backgroundColor: '#ecf0f1',
borderRadius: 3,
width: 300,
height: 40,
padding: 5,
},
button: {
backgroundColor: '#f39c12',
padding: 10,
borderRadius: 3,
marginTop: 10,
},
});
9. The final app should look similar to the following screenshot:
How it works...
The AsyncStorage class allows us to easily save data on the local device. On iOS,
this is accomplished by using dictionaries on text files. On Android, it will use
RocksDB or SQLite, depending on what's available.
It's not recommended to save sensitive information using this method, as the data is not
encrypted.
In step 4, we loaded the current saved data. The AsyncStorage API contains a
getItem method. This method receives the key we want to retrieve as a parameter.
We are using the await/async syntax here since this call is asynchronous. After we
get the value, we just set it to state; this way, we will be able to render the data
on the view.
In step 7, we saved the text from the state. Using the setItem method, we can set a
new key with any value we want. This call is asynchronous, therefore we used
the await/async syntax. You can check the Further reading section of this chapter
for a great article that explains the await/async syntax in depth.
See also
A great article on how async/await in JavaScript works, available at https://ponyfoo.
com/articles/understanding-javascript-async-await.
Retrieving data from a remote API
In the previous chapters, we used the data from a JSON file or directly defined in
the source code. While that worked for our previous recipes, it's rarely very
helpful in real-world applications.
In this recipe, we will learn how to request data from an API. We will fetch a GET
request from an API to get a JSON response. For now, however, we are only
going to display the JSON in a text element. We'll be using the Fake Online
REST API for Testing and Prototyping, hosted at
http://jsonplaceholder.typicode.com and powered by the excellent development test
API software, JSON Server (https://github.com/typicode/json-server).
We will keep this app simple so that we can focus on data management. We will
have a text component that will display the response from the API and also add a
button that requests the data when pressed.
Getting ready
We need to create an empty app. Let's name this one remote-api.
How to do it...
1. Let's start by importing our dependencies into the App.js file:
import React, { Component } from 'react';
import {
StyleSheet,
Text,
TextInput,
TouchableOpacity,
View
} from 'react-native';
2. We are going to define a results property on the state. This property will
hold the response from the API. We'll need to update the view once we get
the response:
export default class App extends Component {
state = {
results: '',
};
// Defined later
}
const styles = StyleSheet.create({
// Defined later
});
3. We'll send the request when the button is pressed. Next, let's create a
method to handle that request:
onLoad = async () => {
this.setState({ results: 'Loading, please wait...' });
const response = await fetch('http://jsonplaceholder.typicode.com/users', {
method: 'GET',
});
const results = await response.text();
this.setState({ results });
}
4. In the render method, we'll display the response, which will be read from the
state. We will use a TextInput to display the API data. Via properties, we'll
declare editing as disabled and support multiline functionality. The button
will call the onLoad function that we created in the previous step:
render() {
const { results } = this.state;
return (
<View style={styles.container}>
<View>
<TextInput
style={styles.preview}
value={results}
placeholder="Results..."
editable={false}
multiline
/>
<TouchableOpacity onPress={this.onLoad} style=
{styles.btn}>
<Text>Load data</Text>
</TouchableOpacity>
</View>
</View>
);
}
5. Finally, we'll add some styles. Again, this will just be the layout, colors,
margins, and padding:
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#fff',
},
preview: {
backgroundColor: '#bdc3c7',
width: 300,
height: 400,
padding: 10,
borderRadius: 5,
color: '#333',
marginBottom: 50,
},
btn: {
backgroundColor: '#3498db',
padding: 10,
borderRadius: 3,
marginTop: 10,
},
});
6. The final app should look similar to the following screenshot:
How it works...
In step 4, we sent the request to the API. We use the fetch method to make the
request. The first parameter is a string with the URL of the endpoint, while
the second parameter is a configuration object. For this request, the only option
we need to define is the request method to GET, but we can also use this object to
define headers, cookies, parameters, and many other things.
We are also using async/await syntax to wait on the response and finally set it on
the state. If you prefer, you could, of course, use promises for this purpose
instead.
Also, note how we are using an arrow function here to properly handle the
scope. This will automatically set the correct scope when this method is assigned
to the onPress callback.
Sending data to a remote API
In the previous recipe, we covered how to get data from an API using fetch. In
this recipe, we will learn how to POST data to the same API. This app will emulate
creating a forum post, and the request for the post will have title, body, and user
parameters.
Getting ready
Before going through this recipe, we need to create a new empty app named
remote-api-post.
In this recipe, we will also be using the very popular axios package for handling
our API requests. You can install it via the Terminal with yarn:
yarn add axios
Alternatively, you can use npm:
npm install axios --save
How to do it...
1. First, we'll need to open the App.js file and import the dependencies we'll be
using:
import React, { Component } from 'react';
import axios from 'axios';
import {
Alert,
ScrollView,
StyleSheet,
Text,
TextInput,
TouchableOpacity,
SafeAreaView,
} from 'react-native';
2. We'll define the App class with a state object that has three properties. The
title and body properties will be used for making the request, and results will
hold the API's response:
const endpoint = 'http://jsonplaceholder.typicode.com/posts';
export default class App extends Component {
state = {
results: '',
title: '',
body: '',
};
const styles = StyleSheet.create({
// Defined later
});
}
3. After saving a new post, we will request all of the posts from the API. We
are going to define an onLoad method to fetch the new data. This code works
just the same as the onLoad method in the previous recipe, but this time, we'll
be using the axios package to create the request:
onLoad = async () => {
this.setState({ results: 'Loading, please wait...' });
const response = await axios.get(endpoint);
const results = JSON.stringify(response);
this.setState({ results });
}
4. Let's work on saving the new data. First, we need to get the values from the
state. We could also run some validations here to make sure that the title
and body are not empty. On the POST request, we need to define the content
type of the request, which, in this case, will be JSON. We will hard code the
userId property to 1. In a real app, we would have probably gotten this value
from a previous API request. After the request has completed, we get the
JSON response, which, if successful, will fire the onLoad method that we
defined previously:
onSave = async () => {
const { title, body } = this.state;
try {
const response = await axios.post(endpoint, {
headers: {
'Content-Type': 'application/json;charset=UTF-8',
},
params: {
userId: 1,
title,
body
}
});
const results = JSON.stringify(response);
Alert.alert('Success', 'Post successfully saved');
this.onLoad();
} catch (error) {
Alert.alert('Error', `There was an error while saving the
post: ${error}`);
}
}
5. The save functionality is complete. Next, we need methods for saving the
title and body to the state. These methods will be executed as the user types
in the input text, keeping track of the values on the state object:
onTitleChange = (title) => this.setState({ title });
onPostChange = (body) => this.setState({ body });
6. We have everything we need for the functionality, so let's add the UI. The
render method will display a toolbar, two input texts, and a Save button for
calling the onSave method that we defined in step 4:
render() {
const { results, title, body } = this.state;
return (
<SafeAreaView style={styles.container}>
<Text style={styles.toolbar}>Add a new post</Text>
<ScrollView style={styles.content}>
<TextInput
style={styles.input}
onChangeText={this.onTitleChange}
value={title}
placeholder="Title"
/>
<TextInput
style={styles.input}
onChangeText={this.onPostChange}
value={body}
placeholder="Post body..."
/>
<TouchableOpacity onPress={this.onSave} style=
{styles.button}>
<Text>Save</Text>
</TouchableOpacity>
<TextInput
style={styles.preview}
value={results}
placeholder="Results..."
editable={false}
multiline
/>
</ScrollView>
</SafeAreaView>
);
}
7. Finally, let's add the styles to define the layout, color, padding, and margins:
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
},
toolbar: {
backgroundColor: '#3498db',
color: '#fff',
textAlign: 'center',
padding: 25,
fontSize: 20,
},
content: {
flex: 1,
padding: 10,
},
preview: {
backgroundColor: '#bdc3c7',
flex: 1,
height: 500,
},
input: {
backgroundColor: '#ecf0f1',
borderRadius: 3,
height: 40,
padding: 5,
marginBottom: 10,
flex: 1,
},
button: {
backgroundColor: '#3498db',
padding: 10,
borderRadius: 3,
marginBottom: 30,
},
});
8. The final app should look similar to the following screenshot:
How it works...
In step 2, we defined three properties on the state. The results property will
contain the response from the server API, which we later use to display the value
in the UI.
We used the title and body properties to hold the values from the input text
components so that the user can create a new post. Those values will then be sent
to the API when pressing the Save button.
In step 6, we declared the elements on the UI. We used two inputs for post data
and the Save button, which calls the onSave method when pressed. Finally, we
used input text to display the result.
Establishing real-time
communication with WebSockets
In this recipe, we will integrate WebSockets in a React Native application. We
are going to use the Hello World of WebSockets applications, that is, a simple
chat app. This app will allow users to send and receive messages.
Getting ready
To support WebSockets on React Native, we will need to run a server to handle
all connected clients. The server should be able to broadcast a message when it
receives a message from any of the connected clients.
We'll start with a new, empty React Native app. We'll name it web-sockets. In the
root of the project, let's add a server folder with an index.js file inside of it. If you
don't already have it, you'll need Node to run the server. You can get Node.js
from https://nodejs.org/ or by using the Node Version Manager (https://github.com/
creationix/nvm).
We'll be using the excellent WebSocket package, ws. You can add the package via
the Terminal with yarn:
yarn add ws
Alternatively, you can use npm:
npm install --save ws
Once you've got the package installed, add the following code to the
/server/index.js file. Once this server is running, it will listen for incoming
connections via server.on('connection') and incoming messages via
socket.on('message'). For more information on how ws works, you can check out the
documentation at https://github.com/websockets/ws:
const port = 3001;
const WebSocketServer = require('ws').Server;
const server = new WebSocketServer({ port });
server.on('connection', (socket) => {
socket.on('message', (message) => {
console.log('received: %s', message);
server.clients.forEach(client => {
if (client !== socket) {
client.send(message);
}
});
});
});
console.log(`Web Socket Server running on port ${port}`);
Once the server code is in place, you can start up the server using Node by
running the following command in the Terminal at the root of the project:
node server/index.js
Leave the server running so that, once we've built the React Native app, we can
use the server to communicate between clients.
How to do it...
1. First, let's create the App.js file and import all the dependencies we'll be
using:
import React, { Component } from 'react';
import {
Dimensions,
ScrollView,
StyleSheet,
Text,
TextInput,
SafeAreaView,
View,
Platform
} from 'react-native';
2. On the state object, we'll declare a history property. This property will be an
array for holding all of the messages that have been sent back and forth
between users:
export default class App extends Component {
state = {
history: [],
};
// Defined in later steps
}
const styles = StyleSheet.create({
// Defined in later steps
});
3. Now, we need to integrate WebSockets into our app by connecting to the
server and setting up the callback functions for receiving messages, errors,
and when the connection is opened or closed. We will do this when the
component has been created, by using the componentWillMount life cycle hook:
componentWillMount() {
const localhost = Platform.OS === 'android' ? '10.0.3.2' :
'localhost';
this.ws = new WebSocket(`ws://${localhost}:3001`);
this.ws.onopen = this.onOpenConnection;
this.ws.onmessage = this.onMessageReceived;
this.ws.onerror = this.onError;
this.ws.onclose = this.onCloseConnection;
}
You will notice that we are declaring localhost to equal 10.0.3.2 if the app is being run on an
Android device. Genymotion has port blocking enabled by default on localhost connections,
but also exposes the IP address 10.0.3.2 to allow port forwarding to localhost. This is solely a
workaround for Genymotion's port blocking. Check the There's more... section at the end of
this recipe for more information.
4. Let's define the callbacks for opened/closed connections and for handling
received errors. We are just going to log the actions, but this is where we
could show an alert message when the connection is closed, or display an
error message when an error is thrown by the server:
onOpenConnection = () => {
console.log('Open!');
}
onError = (event) => {
console.log('onerror', event.message);
}
onCloseConnection = (event) => {
console.log('onclose', event.code, event.reason);
}
5. When receiving a new message from the server, we need to add it to the
history property on the state so that we can render the new content as soon
as it arrives:
onMessageReceived = (event) => {
this.setState({
history: [
...this.state.history,
{ isSentByMe: false, messageText: event.data },
],
});
}
6. Now, on to sending the message. We need to define a method that will get
executed when the user presses the Return key on the keyboard. We need to
do two things at this point: add the new message to history, and then send
the message through the socket:
onSendMessage = () => {
const { text } = this.state;
this.setState({
text: '',
history: [
...this.state.history,
{ isSentByMe: true, messageText: text },
],
});
this.ws.send(text);
}
7. In the previous step, we got the text property from the state. We need to
keep track of the value whenever the user types something into the input, so
we'll need a function for listening to keystrokes and saving the value
to state:
onChangeText = (text) => {
this.setState({ text });
}
8. We have all of the functionality in place, so let's work on the UI. In the
render method, we'll add a toolbar, a scroll view to render all of the messages
in history, and a text input to allow the user to send a new message:
render() {
const { history, text } = this.state;
return (
<SafeAreaView style={[styles.container, android]}>
<Text style={styles.toolbar}>Simple Chat</Text>
<ScrollView style={styles.content}>
{ history.map(this.renderMessage) }
</ScrollView>
<View style={styles.inputContainer}>
<TextInput
style={styles.input}
value={text}
onChangeText={this.onChangeText}
onSubmitEditing={this.onSendMessage}
/>
</View>
</SafeAreaView>
);
}
9. To render the messages from history, we'll loop through the history array and
render each message via the renderMessage method. We'll need to check
whether the current message belongs to the user on this device so that we
can apply the appropriate styles:
renderMessage(item, index){
const sender = item.isSentByMe ? styles.me : styles.friend;
return (
<View style={[styles.msg, sender]} key={index}>
<Text>{item.msg}</Text>
</View>
);
}
10. Finally, let's work on the styles! Let's add styles to the toolbar, the history
component, and the text input. We need to set the history container as
flexible, since we want it to take up all of the available vertical space:
const styles = StyleSheet.create({
container: {
backgroundColor: '#ecf0f1',
flex: 1,
},
toolbar: {
backgroundColor: '#34495e',
color: '#fff',
fontSize: 20,
padding: 25,
textAlign: 'center',
},
content: {
flex: 1,
},
inputContainer: {
backgroundColor: '#bdc3c7',
padding: 5,
},
input: {
height: 40,
backgroundColor: '#fff',
},
// Defined in next step
});
11. Now, on to the styles for each message. We are going to create a common
styles object called msg for all messages, then styles for messages from the
user on the device, and finally, styles for messages from others, changing
the color and alignment accordingly:
msg: {
margin: 5,
padding: 10,
borderRadius: 10,
},
me: {
alignSelf: 'flex-start',
backgroundColor: '#1abc9c',
marginRight: 100,
},
friend: {
alignSelf: 'flex-end',
backgroundColor: '#fff',
marginLeft: 100,
}
12. The final app should look similar to the following screenshot:
How it works...
In step 2, we declared the state object with a history array for keeping track of
messages. The history property will hold objects representing all of the messages
being exchanged between clients. Each object will have two properties: a string
with the message text, and a Boolean flag to determine the sender. We could add
more data here, such as the name of the user, a URL of the avatar image,
or anything else we might need.
In step 3, we connected to the socket provided by the WebSocket server and set
up callbacks for handling socket events. We specified the server address as well
as the port.
In step 5, we defined the callback to execute when a new message is received
from the server. Here, we add a new object to the history array on the state every
time a new message is received. Each message object has the properties
isSentByMe and messageTest.
In step 6, we sent the message to the server. We need to add the message to the
history because the server will broadcast the message to all other clients, but not
the author of the message. To keep track of this message, we need to manually
add it to the history.
There's more...
As mentioned in the Tip following step 3, Genymotion blocks port forwarding
by default. If we just used localhost as the server address, it would not work
properly when the app was running in Genymotion. The easiest workaround is to
use the IP address 10.0.3.2 to bypass this problem. More information can be
found at the following Stack Overflow post, which can be found at https://stackov
erflow.com/questions/20257266/how-to-access-localhost-from-a-genymotion-android-emulator.
You could also use the IP address for your local machine, which would also fix
the localhost port issue in Android. For more information on locating the IP
address of your Mac, go to http://osxdaily.com/2010/11/21/find-ip-address-mac/.
Integrating persistent database
functionality with Realm
As your application becomes more complex, you will likely reach a point where
you need to store data on the device. This could be business data, such as user
lists, to avoid having to make expensive network connections to a Remote API.
Maybe you don't have an API at all and your application works as a self-
sufficient entity. Regardless of the situation, you may benefit from leveraging a
database to store your data. There are multiple options for React Native
applications. The first option is AsyncStorage, which we covered in the Storing and
retrieving data locally recipe in this chapter. You could also consider SQLite, or
you could write an adapter to an OS-specific data provider, such as Core Data.
Another excellent option is using a mobile database, such as Realm. Realm is an
extremely fast, thread-safe, transactional, object-based database. It is primarily
designed for use by mobile devices, with a straightforward JavaScript API. It
supports other features, such as encryption, complex querying, UI bindings, and
more. You can read all about it at https://realm.io/products/realm-mobile-database/.
In this recipe, we will walk through using Realm in React Native. We will create
a simple database and perform basic operations, such as inserting, updating, and
deleting records. We will then display these records in the UI.
Getting ready
Let's create a new empty React Native app named realm-db.
Installing Realm requires running the following command:
react-native link
Because of this, we will be working on an app that is detached from Expo. This
means that you could create this app with the following command:
react-native init
Alternatively, you could create a new Expo app with the following command:
expo init
Then, you can eject the app that was created with Expo via the following
command:
expo eject
Once you've created a React Native app, be sure to install the CocoaPods
dependencies via the ios directory by using cd inside the new app and running the
following:
pod install
Refer to Chapter 10, App Workflow and Third-party Plugins, for a in-depth
explanation of how CocoaPods works, and how ejected (or pure React Native)
applications differ from Expo React Native applications.
In the Sending data to a remote API recipe, we handled our AJAX calls with
the axios package. In this recipe, we will be using the native JavaScript fetch
method for AJAX calls. Either method works just as well, and having exposure
to both will hopefully allow you to decide which you prefer for your projects.
Once you've taken care of creating an ejected app, install Realm with yarn:
yarn add realm
Alternatively, you can use npm:
npm install --save realm
With the package installed, you can link the native packages with the following
code:
react-native link realm
How to do it...
1. First, let's open App.js and import the dependencies we'll be using:
import React, { Component } from 'react';
import {
StyleSheet,
Text,
View,
TouchableOpacity
} from 'react-native';
import Realm from 'realm';
2. Next, we need to instantiate our Realm database, which we'll do in the
componentWillMount method. We'll keep a reference to it by using the realm class
variable:
export default class App extends Component {
realm;
componentWillMount() {
const realm = this.realm = new Realm({
schema: [
{
name: 'User',
properties: {
firstName: 'string',
lastName: 'string',
email: 'string'
}
}
]
});
}
// Defined in later steps.
}
3. To create the User entries, we will use the random user generator API
provided by randomuser.me. Let's create a method with the getRandomUser
function. This will fetch this data:
getRandomUser() {
return fetch('https://randomuser.me/api/')
.then(response => response.json());
}
4. We'll also need a method for creating users in our app. The createUser
method will use the function we defined previously to get a random user,
before saving it to our realm database with the realm.write method and
the realm.create method:
createUser = () => {
const realm = this.realm;
this.getRandomUser().then((response) => {
const user = response.results[0];
const userName = user.name;
realm.write(() => {
realm.create('User', {
firstName: userName.first,
lastName: userName.last,
email: user.email
});
this.setState({users:realm.objects('User')});
});
});
}
5. Since we're interacting with a database, we should also add a function for
updating a User in the database. updateUser will, for simplicity, take the first
record in the collection and change its information:
updateUser = () => {
const realm = this.realm;
const users = realm.objects('User');
realm.write(() => {
if(users.length) {
let firstUser = users.slice(0,1)[0];
firstUser.firstName = 'Bob';
firstUser.lastName = 'Cookbook';
firstUser.email = 'react.native@cookbook.com';
this.setState(users);
}
});
}
6. Finally, let's add a way to delete our users. We'll add a deleteUsers method for
removing all users. This is achieved by calling realm.write with a callback
function that executes realm.deleteAll:
deleteUsers = () => {
const realm = this.realm;
realm.write(() => {
realm.deleteAll();
this.setState({users:realm.objects('User')});
});
}
7. Let's build our UI. We will render a list of User objects and a button for each
of our create, update, and delete methods:
render() {
const realm = this.realm;
return (
<View style={styles.container}>
<Text style={styles.welcome}>
Welcome to Realm DB Test!
</Text>
<View style={styles.buttonContainer}>
<TouchableOpacity style={styles.button}
onPress={this.createUser}>
<Text style={styles.buttontext}>Add User</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.button}
onPress={this.updateUser}>
<Text>Update First User</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.button}
onPress={this.deleteUsers}>
<Text>Remove All Users</Text>
</TouchableOpacity>
</View>
<View style={styles.container}>
<Text style={styles.welcome}>Users:</Text>
{this.state.users.map((user, idx) => {
return <Text key={idx}>{user.firstName} {user.lastName}
{user.email}</Text>;
})}
</View>
</View>
);
}
8. Once we run the app on either platform, our three buttons for interacting
with the database should display over the live data that's saved in our Realm
database:
How it works...
The Realm database is built in C++ and its core is known as the Realm Object
Store. There are products that encapsulate this object store for each major
platform (Java, Objective-C, Swift, Xamarin, and React Native). The React
Native implementation is a JavaScript adapter for Realm. From the React Native
side, we do not need to worry about the implementation details. Instead, we get a
clean API for persisting and retrieving data. The step 5 to step 7 demonstrate
using some basic Realm methods. If you want to see more of what you can do
with the API, check out the documentation for this, which can be found at https:/
/realm.io/docs/react-native/latest/api/.
Masking the application upon
network connection loss
An internet connection is not always available, especially when people are
moving around a city, on the train, or hiking in the mountains. A good user
experience will inform the user when their connection to the internet has been
lost.
In this recipe, we will create an app that shows a message when network
connection is lost.
Getting ready
We need to create an empty app. Let's name it network-loss.
How to do it...
1. Let's start by importing the necessary dependencies into App.js:
import React, { Component } from 'react';
import {
SafeAreaView,
NetInfo,
StyleSheet,
Text,
View,
Platform
} from 'react-native';
2. Next, we'll define the App class and a state object for storing the connectivity
status. The online Boolean will be true if connected, and the offline Boolean
will be true if it isn't:
export default class App extends Component {
state = {
online: null,
offline: null,
};
// Defined in later steps
}
3. After the component has been created, we need to get the initial network
status. We are going to use the NetInfo class's getConnectionInfo method to get
the current status, and we'll also set up a callback that's going to be
executed when the status changes:
componentWillMount() {
NetInfo.getConnectionInfo().then((connectionInfo) => {
this.onConnectivityChange(connectionInfo);
});
NetInfo.addEventListener('connectionChange',
this.onConnectivityChange);
}
4. When the component is about to be destroyed, we need to remove the
listener via the componentWillUnmount life cycle:
componentWillUnmount() {
NetInfo.removeEventListener('connectionChange',
this.onConnectivityChange);
}
5. Let's add the callback that gets executed when the network status changes.
It just checks whether the current network type is none, and sets the
state accordingly:
onConnectivityChange = connectionInfo => {
this.setState({
online: connectionInfo.type !== 'none',
offline: connectionInfo.type === 'none',
});
}
6. Now, we know when the network is on or off, but we still need a UI for
displaying information. Let's render a toolbar with some dummy text as the
content:
render() {
return (
<SafeAreaView style={styles.container}>
<Text style={styles.toolbar}>My Awesome App</Text>
<Text style={styles.text}>Lorem...</Text>
<Text style={styles.text}>Lorem ipsum...</Text>
{this.renderMask()}
</SafeAreaView>
);
}
7. As you can see from the previous step, there's a renderMask function. This
function will return a modal when the network is offline, and nothing if it's
online:
renderMask() {
if (this.state.offline) {
return (
<View style={styles.mask}>
<View style={styles.msg}>
<Text style={styles.alert}>Seems like you do not have
network connection anymore.</Text>
<Text style={styles.alert}>You can still continue
using the app, with limited content.</Text>
</View>
</View>
);
}
}
8. Finally, let's add the styles for our app. We'll start with the toolbar and
content:
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#F5FCFF',
},
toolbar: {
backgroundColor: '#3498db',
padding: 15,
fontSize: 20,
color: '#fff',
textAlign: 'center',
},
text: {
padding: 10,
},
// Defined in next step
}
9. For the disconnection message, we will render a dark mask on top of all
content, and a container with the text at the center of the screen. For the
mask, we need to set the position to absolute, and then set the top, bottom, right,
and left to 0. We'll also add opacity to the mask's background color, and
justify and align the content to the center:
const styles = StyleSheet.create({
// Defined in previous step
mask: {
alignItems: 'center',
backgroundColor: 'rgba(0, 0, 0, 0.5)',
bottom: 0,
justifyContent: 'center',
left: 0,
position: 'absolute',
top: 0,
right: 0,
},
msg: {
backgroundColor: '#ecf0f1',
borderRadius: 10,
height: 200,
justifyContent: 'center',
padding: 10,
width: 300,
},
alert: {
fontSize: 20,
textAlign: 'center',
margin: 5,
}
});
10. To see the mask displayed in the emulators, the emulated device must be
disconnected from the internet. For the iOS simulator, simply disconnect
your Mac's Wi-Fi or unplug the Ethernet to disconnect the simulator from
the internet. On Genymotion, you can disable the Wi-Fi connection of the
phone via the toolbar:
11. Once the device has been disconnected from the internet, the mask should
display accordingly:
How it works...
In step 2, we created the initial state object with two properties: online will be true
when a network connection is available, and offline will be true when it's not
available.
In step 3, we retrieved the initial network status and set up a listener to check
when the status changes. The network type returned by NetInfo will be either wifi,
cellular, unknown, or none. Android also has the extra options of bluetooth, ethernet,
and WiMAX (for WiMAX connections). You can read the documentation to see
all of the available values: https://facebook.github.io/react-native/docs/netinfo.html.
In step 5, we defined the method that will execute whenever the network status
changes, and set the state values of online and offline accordingly. Updating the
state re-renders the DOM, and the mask is displayed if there is no connection.
Synchronizing locally persisted data
with a remote API
When using a mobile app, network connectivity is something that is often taken
for granted. But what happens when your app needs to make an API call, and the
user has just lost connectivity? Fortunately for us, React Native has a module
that reacts to the network connectivity status. We can architect our application in
a way that supports the loss of connectivity by synchronizing our data
automatically as soon as the network connection is restored.
This recipe will show a simple implementation of using the NetInfo module to
control whether or not our application will make an API call. If connectivity is
lost, we will keep a reference of the pending request and complete it when the
network access is restored. We will be
using http://jsonplaceholder.typicode.com again to make a POST request to a live
server.
Getting ready
For this recipe, we will use an empty React Native application named syncing-
data.
How to do it...
1. We'll start this recipe by importing our dependencies into App.js:
import React from 'react';
import {
StyleSheet,
Text,
View,
NetInfo,
TouchableOpacity
} from 'react-native';
2. We'll need to add the pendingSync class variable, which we'll use for storing a
pending request when there is no network connection available. We'll also
create the state object with properties for tracking whether the app is
connected (isConnected), the status of a sync (syncStatus), and the response
from the server after our POST request is made (serverResponse):
export default class App extends React.Component {
pendingSync;
state = {
isConnected: null,
syncStatus: null,
serverResponse: null
}
// Defined in later steps
}
3. In the componentWillMount life cycle hook, we'll get the status of the network
connection via the NetInfo.isConnected.fetch method, setting the
state's isConnected property with the response. We'll also add an event listener
to the connectionChange event for keeping track of changes to the connection:
componentWillMount() {
NetInfo.isConnected.fetch().then(isConnected => {
this.setState({isConnected});
});
NetInfo.isConnected.addEventListener('connectionChange',
this.onConnectionChange);
}
4. Next, let's implement the callback that will be executed by the event listener
we defined in the previous step. In this method, we update the isConnected
property of state. Then, if the pendingSync class variable is defined, it means
we've got a cached POST request, so we'll submit that request and update the
state accordingly:
onConnectionChange = (isConnected) => {
this.setState({isConnected});
if (this.pendingSync) {
this.setState({syncStatus : 'Syncing'});
this.submitData(this.pendingSync).then(() => {
this.setState({syncStatus : 'Sync Complete'});
});
}
}
5. Next, we need to implement a function that will actually make the API call
when there is an active network connection:
submitData(requestBody) {
return fetch('http://jsonplaceholder.typicode.com/posts', {
method : 'POST',
body : JSON.stringify(requestBody)
}).then((response) => {
return response.text();
}).then((responseText) => {
this.setState({
serverResponse : responseText
});
});
}
6. The last thing we need to do before we can work on our UI is a function for
handling the onPress event on the Submit Data button we will be rendering.
This will either perform the call immediately or be saved in this.pendingSync
if there is no network connection:
onSubmitPress = () => {
const requestBody = {
title: 'foo',
body: 'bar',
userId: 1
};
if (this.state.isConnected) {
this.submitData(requestBody);
} else {
this.pendingSync = requestBody;
this.setState({syncStatus : 'Pending'});
}
}
7. Now, we can build out our UI, which will render the Submit Data button
and show the current connection status, sync status, and most recent
response from the API:
render() {
const {
isConnected,
syncStatus,
serverResponse
} = this.state;
return (
<View style={styles.container}>
<TouchableOpacity onPress={this.onSubmitPress}>
<View style={styles.button}>
<Text style={styles.buttonText}>Submit Data</Text>
</View>
</TouchableOpacity>
<Text style={styles.status}>
Connection Status: {isConnected ? 'Connected' :
'Disconnected'}
</Text>
<Text style={styles.status}>
Sync Status: {syncStatus}
</Text>
<Text style={styles.status}>
Server Response: {serverResponse}
</Text>
</View>
);
}
8. You can disable the network connection in the simulator in the same way as
described in step 10 of the previous recipe:
How it works...
This recipe leverages the NetInfo module to control when an AJAX request
should be made.
In step 6, we defined the method that's executed when the Submit Data button is
pressed. If there is no connectivity, we save the request body into
the pendingSync class variable.
In step 3, we defined the componentWillMount life cycle hook. Here, two NetInfo
method calls retrieve the current network connection status and attach an event
listener to the change event.
In step 4, we defined the function that will be executed whenever the network
connection has changed, which informs the state's isConnected Boolean property
appropriately. If the device is connected, we also check to see whether there is a
pending API call, and complete the request if it exists.
This recipe could also be expanded on to support a queue system of pending
calls, which would allow multiple AJAX requests to be delayed until an internet
connection was re-established.
Logging in with Facebook
Facebook is the largest social media platform in existence, with well over 1
billion users worldwide. This means that there's a good chance that your users
will have a Facebook account. Your app can register and link with their account,
allowing you to use their Facebook credentials as a login for your app.
Depending on the requested permissions, this will also allow you to access data
such as user information, and pictures, and even give you the ability to access
shared content. You can read more about the available permission options from
the Facebook docs at https://developers.facebook.com/docs/facebook-login/permissions#re
ference-public_profile.
In this recipe, we will cover a basic method for logging into Facebook via an app
to get a session token. We'll then use that token to access the basic /me endpoint
provided by Facebook's Graph API, which will give us the user's name and
ID. For more complex interactions with the Facebook Graph API, you can look
at the documentation, which can be found at https://developers.facebook.com/docs/gra
ph-api/using-graph-api.
To keep this recipe simple, we will be building an Expo app that uses
the Expo.Facebook.logInWithReadPermissionsAsync method to do the heavy lifting of
logging into Facebook, which will also allow us to bypass much of the setup
that's otherwise necessary for such an app. If you wish to interact with Facebook
without using Expo, you will likely want to use the React Native Facebook
SDK, which requires a lot more steps. You can find the SDK at https://github.com/
facebook/react-native-fbsdk.
Getting ready
For this recipe, we'll create a new app called facebook-login. You will need to have
an active Facebook account to test its functionality.
A Facebook Developer account is also necessary for this recipe. Head over to htt
ps://developers.facebook.com to sign up if you don't have one. Once you are logged
in, you can use the dashboard to create a new app. Make note of the app ID once
it's been created, as we'll need it for the recipe.
How to do it...
1. Let's start by opening the App.js file and adding our imports:
import React from 'react';
import {
StyleSheet,
Text,
View,
TouchableOpacity,
Alert
} from 'react-native';
import Expo from 'expo';
2. Next, we'll declare the App class and add the state object. The state will keep
track of whether the user is logged in with the loggedIn Boolean, and will
save the retrieved user data from Facebook in an object called
facebookUserInfo:
export default class App extends React.Component {
state = {
loggedIn: false,
facebookUserInfo: {}
}
// Defined in later steps
}
3. Next, let's define the logIn method of our class. This will be the method
that's called when the Login button is pressed. This method uses
the logInWithReadPermissionsAsync Expo helper class of the Facebook method to
prompt the user with a Facebook login screen. Replace the first parameter,
labeled APP_ID in the following code, with your App's ID:
logIn = async () => {
const { type, token } = await
Facebook.logInWithReadPermissionsAsync(APP_ID, {
permissions: ['public_profile'],
});
// Defined in next step
}
4. In the second half of the logIn method, if the request is successful, we'll
make a call to the Facebook Graph API using the token that was received
from logging in to request the logged-in user's information. Once the
response resolves, we set the state accordingly:
logIn = async () => {
//Defined in step above
if (type === 'success') {
const response = await fetch(`https://graph.facebook.com/me?
access_token=${token}`);
const facebookUserInfo = await response.json();
this.setState({
facebookUserInfo,
loggedIn: true
});
}
}
5. We'll also need a simple render function. We'll display a Login button for
logging in, as well as Text elements that will display user information once
the login has completed successfully:
render() {
return (
<View style={styles.container}>
<Text style={styles.headerText}>Login via Facebook</Text>
<TouchableOpacity
onPress={this.logIn}
style={styles.button}
>
<Text style={styles.buttonText}>Login</Text>
</TouchableOpacity>
{this.renderFacebookUserInfo()}
</View>
);
}
6. As you can see in the preceding render function, we're calling
this.renderFacebookUserInfo to render user information. This method simply
checks whether the user in logged in via this.state.loggedIn. If they are, we'll
display the user's information. If not, we'll return null to display nothing:
renderFacebookUserInfo = () => {
return this.state.loggedIn ? (
<View style={styles.facebookUserInfo}>
<Text style={styles.facebookUserInfoLabel}>Name:</Text>
<Text style={styles.facebookUserInfoText}>
{this.state.facebookUserInfo.name}</Text>
<Text style={styles.facebookUserInfoLabel}>User ID:</Text>
<Text style={styles.facebookUserInfoText}>
{this.state.facebookUserInfo.id}</Text>
</View>
) : null;
}
7. Finally, we'll add styles to complete the layout, setting padding, margins,
color, and font sizes:
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
button: {
marginTop: 30,
padding: 10,
backgroundColor: '#3B5998'
},
buttonText: {
color: '#fff',
fontSize: 30
},
headerText: {
fontSize: 30
},
facebookUserInfo: {
paddingTop: 30
},
facebookUserInfoText: {
fontSize: 24
},
facebookUserInfoLabel: {
fontSize: 20,
marginTop: 10,
color: '#474747'
}
});
8. Now, if we run the app, we'll see our Login button, a login modal when the
Login button is pressed, and the user's information, which will be displayed
once the user has successfully logged in:
How it works...
Interacting with Facebook in our React Native app is made much easier than it
otherwise would be, via Expo's Facebook helper library.
In step 5, we created the logIn function, which uses
Facebook.logInWithReadPermissionsAsync to make the login request to Facebook. It
takes two parameters: an appID and an options object. In our case, we're only
setting the permissions option. The permissions option takes an array of strings
for each type of permission requested, but for our purpose, we only use the most
basic permission, 'public_profile'.
In step 6, we completed the logIn function. It makes a call to Facebook's Graph
API endpoint, /me, upon successful login, using the token provided by the data
that's returned from logInWithReadPermissionsAsync. The user's information and the
login status are saved to state, which will trigger a re-render and display the
user's data on the screen.
This recipe intentionally only makes a call to one simple API endpoint. You
could use the return data from this endpoint to populate user data in your app.
Alternatively, you could use the same token that was received from logging in to
perform any actions provided by the Graph API. To see what kind of data is at
your disposal via the API, you can view the reference docs at https://developers.fa
cebook.com/docs/graph-api/reference.
Implementing Redux
In this chapter, we'll go step by step through the process of adding Redux to our
app. We'll cover the following recipes:
Installing Redux and preparing our project
Defining actions
Defining reducers
Setting up the store
Communicating with a remote API
Connecting the store to the views
Storing offline content using Redux
Showing network connectivity status
Introduction
At some point during the development of most applications, we'll need a better
way to handle the state of the overall app. This will ease sharing data across
components and provide a more robust architecture for scaling our app in the
future.
In order to get a better understanding of Redux, the structure of this chapter will
differ from previous chapters, since we'll be creating one app through all of these
recipes. Each recipe in this chapter will depend on the last recipe.
We will be building a simple app for displaying user posts, and we'll use a
ListView component to display the data returned from the API. We'll be using the
excellent mock data API we've used before located at https://jsonplaceholder.typico
de.com.
Installing Redux and preparing our
project
In this recipe, we'll install Redux in an empty application, and we'll define the
basic folder structure of our app.
Getting started
We'll need a new empty app for this recipe. Let's call it redux-app.
We'll also need two dependencies: redux for handling state management and react-
redux for gluing together Redux and React Native. You can install them from the
command line with yarn:
yarn add redux react-redux
Or you can use npm:
npm install --save redux react-redux
How to do it...
1. As part of this recipe, we'll build out the folder structure that the app will
use. Let's add a components folder with an Album folder inside of it to hold the
photo album component. We'll also need a redux folder to hold all of our
Redux code.
2. Inside the redux folder, let's add an index.js file for Redux initialization. We
also need a photos directory, with an actions.js file and a reducer.js file.
3. For now, the App.js file will only contain an Album component, which we'll
define later:
import React, { Component } from 'react';
import { StyleSheet, SafeAreaView } from 'react-native';
import Album from './components/Album';
const App = () => (
<SafeAreaView style={styles.container}>
<Album />
</SafeAreaView>
);
const styles = StyleSheet.create({
container: {
flex: 1,
},
});
export default App;
How it works...
In Getting started, we installed the redux and react-redux libraries. The react-redux
library contains the necessary bindings to integrate Redux with React. Redux is
not exclusively designed to work with React. You can use Redux with any other
JavaScript libraries out there. By using react-redux, we'll be able to seamlessly
integrate Redux into our React Native application.
In step 2, we created the main folders we'll use for our app:
The components folder will contain our app components. In this case, we're
only adding one Album component to keep this recipe simple.
The redux folder will contain all of the Redux related code (initialization,
actions, and reducers).
In a medium to large app, you will probably want to separate your React Native
components further. The React community standard is to split the app's
components into three separate types:
Components: The community calls them presentational components. In simple
terms, these are the kind of components that are not aware of any business
logic or Redux actions. These components only receive data via props and
should be reusable on any other project. A button or panel would be a
perfect example of a presentational component.
Containers: These are components that directly receive data from Redux and
are able to call actions. In here, we'll define components such as a header
that displays the logged in user. Usually, these components internally use
presentational components.
Pages/Views: These are the main modules in the app that use containers and
presentational components.
For more information on structuring your Redux powered components, I
recommend the excellent article, Structure your React-Redux project for
scalability and maintainability, at the following link:
https://levelup.gitconnected.com/structure-your-react-redux-project-for-scalability-and-ma
intainability-618ad82e32b7
We also created a redux/photos folder. In this folder, we'll create the following:
The actions.js file, which will contain all of the actions the app can perform.
We will talk more about actions on the next recipe.
The reducer.js file, which will contain all the code managing the data in the
Redux store. We will dig deeper into this subject in later recipes.
Defining actions
An action is a payload of information that sends data to the store. Using these
actions is the only way components can request or send data to the Redux store,
which serves as the global state object for the entire app. An action is just a plain
JavaScript object. We'll be defining functions that return these actions. A
function that returns an action is called an action creator.
In this recipe, we'll create the actions to load the initial images for the gallery.
During this recipe, we'll be adding hardcoded data, but later on, we'll request this
data from an API to create a more realistic scenario.
Getting ready
Let's continue working on the code from the previous recipe. Make sure to
follow those steps in order to have Redux installed and build out the folder
structure that we'll use for this project.
How to do it...
1. We'll need to define types for each the action. Open the
redux/photos/actions.js file. Action types are defined as constants that can
later be referenced in actions and reducers, as follows:
export const FETCH_PHOTOS = 'FETCH_PHOTOS';
2. Now let's create our first action creator. Every action needs a type property
to define it, and actions will often have a payload property of data to pass
along with the action. In this recipe, we're hardcoding a mock API response
made up of an array of two photo objects, as follows:
export const fetchPhotos = () => {
return {
type: FETCH_PHOTOS,
payload: {
"photos": [
{
"albumId": 2,
"title": "dolore esse a in eos sed",
"url": "http://placehold.it/600/f783bd",
"thumbnailUrl": "http://placehold.it/150/d83ea2",
"id": 2
},
{
"albumId": 2,
"title": "dolore esse a in eos sed",
"url": "http://placehold.it/600/8e6eef",
"thumbnailUrl": "http://placehold.it/150/bf6d2a",
"id": 3
}
]
}
}
}
3. We will need an action creator for each action we want the app to be able to
execute, and we want this app to be able to add and remove images. First,
let's add the addBookmark action creator, as follows:
export const ADD_PHOTO = 'ADD_PHOTO';
export const addPhoto = (photo) => {
return {
type: ADD_PHOTO,
payload: photo
};
}
4. Likewise, we'll need another action creator for removing photos:
export const REMOVE_PHOTO = 'REMOVE_PHOTO';
export const removePhoto = (photo) => {
return {
type: REMOVE_PHOTO,
payload: photo
};
}
How it works...
In step 1, we defined the action's type to indicate what it does, which in this case
is fetch images. We use a constant since it will be used in multiple places,
including action creators, reducers, and tests.
In step 2, we declared an action creator. Actions are simple JavaScript objects
that define an event that happens in our app that will affect the state of the app.
We use actions to interact with data that lives in the Redux store.
There's only one single requirement: each action must have a type property. In
addition, an action will often include a payload property that holds data relevant to
the action. In this case, we are using an array of photo objects.
An action is valid as long as the type property is defined. If we want to send anything else, it is
a common convention to use the payload property as popularized by the flux pattern. However,
the name property isn't inherently special. We could name this params or data and the behavior
would remain the same.
There's more...
Currently, we have defined the action creators, which are simple functions that
return actions. In order to use them, we need to use the dispatch method provided
by the Redux store. We will learn more about the store in later recipes.
Defining reducers
At this point, we have created a few actions for our app. As discussed earlier,
actions define that something should happened, but we haven't created anything
for putting the action into motion. That's where reducers come in. Reducers are
functions that define how an action should affect the data in the Redux store. All
accessing of data in the store happens in a reducer.
Reducers receive two parameters: state and action. The state parameter represents
the global state of the app, and the action parameter is the action object being
used by the reducer. Reducers return a new state parameter reflecting the
changes that are associated with a given action parameter. In this recipe, we'll
introduce a reducer for fetching the photos by using the actions we defined in the
previous recipe.
Getting ready
This recipe depends on the previous recipe, Defining actions. Be sure to start
from the beginning of this chapter to avoid any problems or confusion.
How to do it...
1. Let's start by opening the photos/reducer.js file and importing all of the action
types we defined in the previous recipe, as follows:
import {
FETCH_PHOTOS,
ADD_PHOTO,
REMOVE_PHOTO
} from './actions';
2. We'll define an initial state object for the state in this reducer. It has a photos
property initialized to an empty array for the currently loaded photos, as
follows:
const initialState = () => return {
photos: []
};
3. We can now define the reducer function. It'll receive two parameters, the
current state and the action that has been dispatched, as follows:
export default (state = initialState, action) => {
// Defined in next steps
}
React Native components can also have a state object, but that is an entirely separate state
from that which Redux uses. In this context, state refers to the global state stored in the
Redux store.
4. State is immutable, so instead of manipulating state, inside the reducer
function, we need to return a new state for the current action, as follows:
export default (state = initialState, action) => {
switch (action.type) {
case FETCH_PHOTOS:
return {
...state,
photos: [...action.payload],
};
// Defined in next steps
}
5. In order to add a new bookmark to the array, all we need to do is get the
payload of the action and include it in the new array. We can use the spread
operator to spread the current photos array on state, then
add action.payload to the new array, as follows:
case ADD_PHOTO:
return {
...state,
photos: [...state.photos, action.payload],
};
6. If we want to remove an item from the array, we can use the filter method,
as follows:
case REMOVE_PHOTO:
return {
...state,
photos: state.photos.filter(photo => {
return photo.id !== action.payload.id
})
};
7. The final step is to combine all of the reducers that we have. In a larger app,
you will likely have reason to break your reducers into separate files. Since
we're only using one reducer, this step is technically optional, but it
illustrates how multiple reducers can be combined together with
Redux's combineReducers helper. Let's use it in the redux/index.js file, which
we'll also use to initiate the Redux store in the next recipe, as follows:
import { combineReducers } from 'redux';
import photos from './photos/reducers';
const reducers = combineReducers({
photos,
});
How it works...
In step 1, we imported all of the action types that we declared in the previous
recipe. We use these types to determine what action should be taken and
how action.payload should affect the Redux state.
In step 2, we defined the initial state of the reducer function. For now, we only
need an empty array for our photos, but we could add other properties to the
state, such as Boolean properties of isLoading and didError to track loading and
error states. These can, in turn, be used to update the UI during and in response
to async actions.
In step 3, we defined the reducer function, which receives two parameters: the
current state and the action that is being dispatched. We set the initial state
to initialState if we are not provided with one. This way, we can ensure that the
photos array exists at all times within the app, which will help in avoiding errors
in cases where actions get dispatched that don't affect the Redux state.
In step 4, we defined an action for fetching photos. Remember that state is never
directly manipulated. If the action's type matches the case, a new state object is
created by combining the current state.photos array with the incoming photos
on action.payload.
The reducer function should be pure. This means there shouldn't be side effects on
any of the input values. Mutating the state or the action is bad practice and
should always be avoided. A mutation can lead to inconsistent data or not trigger
a render correctly. Also, in order to prevent side effects, we should avoid
executing any AJAX requests inside the reducer.
In step 5, we created the action for adding a new element to the photos array, but
instead of using Array.push, we are returning a new array and appending the
incoming element to the last position to avoid mutating the original array on the
state.
In step 6, we added an action for removing the bookmark from the state. The
easiest way to do this is by using the filter method so we can ignore the element
with the ID that was received on the action's payload.
In step 7, we use the combineReducers function to merge all of the reducers into a
single global state object that will be saved in the store. This function will call
each reducer with the key in the state that corresponds to that reducer; this
function is exactly the same as the following:
import photosReducer from './photos/reducer';
const reducers = function(state, action) {
return {
photos: photosReducer(state.photos, action),
};
}
The photos reducer has only been called on the part of the state that cares about
photos. This will help you avoid managing all state data in a single reducer.
Setting up the Redux store
The Redux store is responsible for updating the information that is calculated on
the state inside reducers. It is a single global object, which can be accessed via
the store's getState method.
In this recipe, we'll tie together the actions and the reducer we created in
previous recipes. We will use the existing actions to affect data that lives in the
store. We will also learn how to log changes on the state by subscribing to the
store changes. This recipe serves more as a proof of concept of how actions,
reducers, and the store work together. We'll dive deeper into how Redux is more
commonly used within apps later in this chapter.
How to do it...
1. Let's open the redux/index.js file and import the createStore function
from redux, as follows:
import { combineReducers, createStore } from 'redux';
2. Creating the store is extremely simple; all we need to do is call the function
from step 1 and send the reducers as the first parameter, as follows:
const store = createStore(reducers);
export default store;
3. That's it! We've set up the store, so now let's dispatch some actions. The
next steps in this recipe will be removed from the final project since they're
for testing our setup. Let's start by importing the action creators we would
like to dispatch:
import {
loadPhotos,
addPhotos,
removePhotos,
} from './photos/actions';
4. Before dispatching any action, let's subscribe to the store, which will allow
us to listen to any changes that occur in the store. For our current purposes,
we only need to console.log the result of store.getState(), as follows:
const unsubscribe = store.subscribe(() => {
console.log(store.getState());
});
5. Let's dispatch some actions and see the resulting state in the Developer
console:
store.dispatch(loadPhotos());
6. In order to add a new bookmark, we need to dispatch the addBookmark action
creator with the photos object as the parameter:
store.dispatch(addPhoto({
"albumId": 2,
"title": "dolore esse a in eos sed",
"url": `http://placehold.it/600/`,
"thumbnailUrl": `http://placehold.it/150/`
}));
7. To remove an item, we pass along the id of the photo we want to remove to
the action creator, since this is what the reducer is using to find the item that
should be deleted:
store.dispatch(removePhoto({ id: 1 }));
8. After executing all of these actions, we can stop listening to changes on the
store by running the unsubscribe function we created in step 4 when we
subscribed to the store, as follows:
unsubscribe();
9. We need to import the redux/index.js file into the App.js file, which will run
all of the code in this recipe so we can see the related console.log messages
in the Developer console:
import store from './redux';
How it works...
In step 3, we imported the action creators we created in the earlier
recipe, Defining actions. Even though we don't yet have a UI, we can use the
Redux store and observe the changes as they happen. All it takes is calling an
action creator and then dispatching the resulting action.
In step 5, we called the dispatch method from the store instance. dispatch takes an
action, which is created by the loadBookmarks action creator. The reducer will be
called in turn, which will set the new photos on the state.
Once we have our UI in place, we'll dispatch the actions in a similar fashion
from our components, which will update the state, ultimately triggering a re-
render of the component, displaying the new data.
Communicating with a remote API
We are currently loading the bookmarks from hardcoded data in the action. In a
real app, we're much more likely to be getting data back from an API. In this
recipe, we'll use a Redux middleware to help with the process of fetching data
from an API.
Getting ready
In this recipe, we'll be using axios to make all AJAX requests. Install it with npm:
npm install --save axios
Or you can install it with yarn:
yarn add axios
For this recipe, we'll be using the Redux middleware, redux-promise-middleware.
Install the package with npm:
npm install --save redux-promise-middleware
Or you can install it with yarn:
yarn add redux-promise-middleware
This middleware will create and automatically dispatch three related actions for
each AJAX request made in our app: one when a request begins, one when a
request succeeds, and one for when a request fails. Using this middleware, we
are able to define an action creator that returns an action object with a promise
for a payload. In our case, we'll be creating the async action, FETCH_PHOTOS, whose
payload is an API request. The middleware will create and dispatch an action of
the FETCH_PHOTOS_PENDING type. When the request resolves, the middleware will
create and dispatch either an action of the FETCH_PHOTOS_FULFILLED type with the
resolved data as the payload if the request was successful or an action of
the FETCH_PHOTOS_REJECTED type with the error as a payload if the request failed.
How to do it...
1. Let's start by adding the new middleware to our Redux store. In
the redux/index.js file, let's add the Redux method, applyMiddleware. We'll also
add the new middleware we just installed, as follows:
import { combineReducers, createStore, applyMiddleware } from 'redux';
import promiseMiddleware from 'redux-promise-middleware';
2. In the call to createStore that we defined previously, we can pass
in applyMiddleware as the second parameter. applyMiddleware takes one
parameter, which is the middleware we want to use, promiseMiddleware:
const store = createStore(reducers, applyMiddleware(promiseMiddleware()));
Unlike some other popular Redux middleware solutions such as redux-thunk, promiseMiddleware
must be invoked when it is passed to applyMiddleware. It is a function that returns the
middleware.
3. We're going to be making real API requests in our actions now, so we need
to import axios into redux/photos/actions. We'll also add the API's base URL.
We are using the same dummy data API we used in previous chapters,
hosted at http://jsonplaceholder.typicode.com, as follows:
import axios from 'axios';
const API_URL='http://jsonplaceholder.typicode.com';
4. Next, we'll update our action creators. We'll first update the types we need
for handling AJAX requests, as follows:
export const FETCH_PHOTOS = 'FETCH_PHOTOS';
export const FETCH_PHOTOS_PENDING = 'FETCH_PHOTOS_PENDING';
export const FETCH_PHOTOS_FULFILLED = 'FETCH_PHOTOS_FULFILLED';
export const FETCH_PHOTOS_REJECTED = 'FETCH_PHOTOS_REJECTED';
5. Instead of returning dummy data as payload for this action, we'll return a GET
request. Since this is a Promise, it will trigger our new middleware. Also,
notice how the action's type is FETCH_PHOTOS. This will cause the middleware
to automatically create FETCH_PHOTOS_PENDING, FETCH_PHOTOS_FULFILLED with a payload
of resolved data when successful, and FETCH_PHOTOS_REJECTED with a payload of
the error that occurred, as follows:
export const fetchPhotos = () => {
return {
type: FETCH_PHOTOS,
payload: axios.get(`${API_URL}/photos?_page=1&_limit=20`)
}
}
6. Just like the FETCH_PHOTOS action, we'll be making use of the same middleware
provided types for the ADD_PHOTO action, as follows:
export const ADD_PHOTO = 'ADD_PHOTO';
export const ADD_PHOTO_PENDING = 'ADD_PHOTO_PENDING';
export const ADD_PHOTO_FULFILLED = 'ADD_PHOTO_FULFILLED';
export const ADD_PHOTO_REJECTED = 'ADD_PHOTO_REJECTED';
7. The action creator itself will no longer just return the passed in photo as the
payload, but instead will pass a POST request promise for adding the image via
the API, as follows:
export const addPhoto = (photo) => {
return {
type: ADD_PHOTO,
payload: axios.post(`${API_URL}/photos`, photo)
};
}
8. We can follow the same pattern to convert the REMOVE_PHOTO action into an
AJAX request that uses the API to delete a photo. Like the other two action
creators for ADD_PHOTO and FETCH_PHOTOS, we'll define the action types for each
action, then return the delete axios request as the action's payload. Since we'll
need photoId in the reducer when we remove the image object from the
Redux store, we also pass that along as an object on the
action's meta property, as follows:
export const REMOVE_PHOTO = 'REMOVE_PHOTO';
export const REMOVE_PHOTO_PENDING = 'REMOVE_PHOTO_PENDING';
export const REMOVE_PHOTO_FULFILLED = 'REMOVE_PHOTO_FULFILLED';
export const REMOVE_PHOTO_REJECTED = 'REMOVE_PHOTO_REJECTED';
export const removePhoto = (photoId) => {
console.log(`${API_URL}/photos/${photoId}`);
return {
type: REMOVE_PHOTO,
payload: axios.delete(`${API_URL}/photos/${photoId}`),
meta: { photoId }
};
}
9. We also need to revisit our reducers to adjust the expected payload.
In redux/reducers.js, we'll start by importing all of the action types we'll be
using, and we'll update initialState. For reasons that will be apparent in the
next recipe, let's rename the array of photos on the state object
to loadedPhotos, as follows:
import {
FETCH_PHOTOS_FULFILLED,
ADD_PHOTO_FULFILLED,
REMOVE_PHOTO_FULFILLED,
} from './actions';
const initialState = {
loadedPhotos: []
};
10. In the reducer itself, update each case to take the FULFILLED variation of the
base action: FETCH_PHOTOS becomes FETCH_PHOTOS_FULFILLED, ADD_PHOTOS
becomes ADD_PHOTOS_FULFILLED, and REMOVE_PHOTOS becomes REMOVE_PHOTOS_FULFILLED.
We'll also update all of the references to the photos array of state from photos
to loadedPhotos. When using axios, all response objects will contain a data
parameter that holds the actual data received from the API, which means
we'll also need to update all references of action.payload to action.payload.data.
And in the REMOVE_PHOTO_FULFILLED reducer, we can no longer find photoId
at action.payload.id, which is why we passed photoId on the action's meta
property in step 8, therefore action.payload.id becomes action.meta.photoId, as
follows:
export default (state = initialState, action) => {
switch (action.type) {
case FETCH_PHOTOS_FULFILLED:
return {
...state,
loadedPhotos: [...action.payload.data],
};
case ADD_PHOTO_FULFILLED:
return {
...state,
loadedPhotos: [action.payload.data, ...state.loadedPhotos],
};
case REMOVE_PHOTO_FULFILLED:
return {
...state,
loadedPhotos: state.loadedPhotos.filter(photo => {
return photo.id !== action.meta.photoId
})
};
default:
return state;
}
}
How it works...
In step 2, we applied the middleware that was installed in the Getting started
section. As mentioned before, this middleware will allow us to make just one
action creator for AJAX actions that automatically creates individual action
creators for the PENDING, FULFILLED, and REJECTED request states.
In step 5, we defined the fetchPhotos action creator. You'll recall from the previous
recipes that actions are plain JavaScript objects. Since we defined a Promise on
the action's payload property, redux-promise-middleware will intercept this action and
automatically create the three associated actions for the three possible request
states.
In step 7 and step 8, we defined the addPhoto action creator and the removePhoto
action creator which, just like fetchPhotos, have an AJAX request as the action
payload.
By utilizing this middleware, we are able to avoid repeating the same boilerplate
over and over for making different AJAX requests.
In this recipe, we only handled the success conditions of the AJAX requests
made in the app. It would be wise in a real app to also handle the error states
represented with actions types ending in _REJECTED. This will be a great place to
handle an error by saving it to the Redux store, so that the view can display error
information when it occurs.
Connecting the store to the view
So far, we have set up the state, we have included middleware, and we've
defined actions, action creators, and reducers for interacting with a remote API.
However, we are not able to show any of this data on the screen. In this recipe,
we'll enable our component to access the store that we have created.
Getting ready
This recipe depends on all of the previous ones, so make sure to follow each
recipe preceding this one.
In the first recipe of this chapter, we installed the react-redux library along with
our other dependencies. In this recipe, we are finally going to make use of it.
We'll also be using a third-party library for generating random color hexes,
which we'll use to request colored images from the placeholder image service at
https://placehold.it/. Before we begin, install randomcolor with npm:
npm install --save randomcolor
Or you can install it with yarn:
yarn add randomcolor
How to do it...
1. Let's start by wiring the Redux store to the React Native app in App.js. We'll
start with the imports, importing Provider from react-redux and the store we
created earlier. We'll also import the Album component we'll be defining
shortly, as follows:
import React, { Component } from 'react';
import { StyleSheet, SafeAreaView } from 'react-native';
import { Provider } from 'react-redux';
import store from './redux';
import Album from './components/Album';
2. It's the job of e prop, where we'll pass in our Redux store. The app and th
Provider to connect our Redux store to the React Native app so that the app's
components can communicate with the store. Provider should be used to
wrap the entire app, and since this app lives in the Album component, we'll
wrap the Album component with the Provider component. Provider takes a store
prop, where we'll pass in our Redux store. The app and the store are wired:
const App = () => (
<Provider store={store}>
<Album />
</Provider>
);
export default App;
3. Let's turn to the Album component. The component will live
at components/Album/index.js. We'll start with the imports. We'll import the
randomcolor package for generating random color hexes, as mentioned in
the Getting started section. We'll also import connect from react-redux, and the
action creators we defined in previous recipes. connect will wire our app to
the Redux store, and we can then use the action creators to affect the store's
state, as follows:
import React, { Component } from 'react';
import {
StyleSheet,
Text,
View,
SafeAreaView,
ScrollView,
Image,
TouchableOpacity
} from 'react-native';
import randomColor from 'randomcolor';
import { connect } from 'react-redux';
import {
fetchPhotos,
addPhoto,
removePhoto
} from '../../redux/photos/actions';
4. Let's create the Album class, however, instead of directly exporting Album as
the default export, we'll use connect to wire Album to the store. Note that connect
is called with two sets of parentheses and that the component is passed into
the second set, as follows:
class Album extends Component {
}
export default connect()(Album);
5. The first set of parentheses in a call to connect takes two function
parameters: mapStateToProps and mapDispatchToProps. We'll define mapStateToProps
first, which takes state as a parameter. This state is our global Redux state
object containing all of our data. The function returns an object of the
pieces of state that we want to use in our component. In our case, we just
need the loadedPhotos property from the photos reducer. By setting this value
to photos in the return object, we can expect this.props.photos to be the value
stored in state.photos.loadedPhotos. And it will change automatically when the
Redux store is updated:
class Album extends Component {
}
const mapStateToProps = (state) => {
return {
photos: state.photos.loadedPhotos
}
}
export default connect(mapStateToProps)(Album);
6. Similarly, the mapDispatchToProps function will map our action creators to the
component's props as well. The function receives the Redux
method, dispatch, which is used to execute an action creator. We'll map the
execution of each action creator to a key of the same name, so
that this.props.fetchPhotos() will execute dispatch(fetchPhotos()), and so on, as
follows:
class Album extends Component {
}
const mapStateToProps = (state) => {
return {
photos: state.photos.loadedPhotos
}
}
const mapDispatchToProps = (dispatch) => {
return {
fetchPhotos: () => dispatch(fetchPhotos()),
addPhoto: (photo) => dispatch(addPhoto(photo)),
removePhoto: (id) => dispatch(removePhoto(id))
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Album);
7. Now that we've got our Redux store wired to our component, let's create the
component itself. We can make use of the componentDidMount life cycle hook to
fetch our photos, as follows:
class Album extends Component {
componentDidMount() {
this.props.fetchPhotos();
}
// Defined on later steps
}
8. We will also need a method for adding photos. Here, we'll use the randomcolor
package (imported as randomColor by convention) to create an image with the
placehold.it service. The generated color string comes back with a hash #
prefixing the hex value, which the request to the image service doesn't
want, so we can simply remove it with a replace call. To add the photo, we
just call the addPhoto function mapped to props, passing in the new photo
object, as follows:
addPhoto = () => {
const photo = {
"albumId": 2,
"title": "dolore esse a in eos sed",
"url": `http://placehold.it/600/${randomColor().replace('#',
'')}`,
"thumbnailUrl":
`http://placehold.it/150/${randomColor().replace('#', '')}`
};
this.props.addPhoto(photo);
}
9. We will also need a removePhoto function. All this function needs to do is call
the removePhoto function that has been mapped to props, passing in the ID of
the photo to be removed, as follows:
removePhoto = (id) => {
this.props.removePhoto(id);
}
10. The template for the app will need a TouchableOpacity button for adding
photos, a ScrollView for holding all of the images in a scrollable list, and all
of our images. Each Image component will also be wrapped in
a TouchableOpacity component for calling the removePhoto method when an
image is pressed, as follows:
render() {
return (
<SafeAreaView style={styles.container}>
<Text style={styles.toolbar}>Album</Text>
<ScrollView>
<View style={styles.imageContainer}>
<TouchableOpacity style={styles.button} onPress=
{this.addPhoto}>
<Text style={styles.buttonText}>Add Photo</Text>
</TouchableOpacity>
{this.props.photos ? this.props.photos.map((photo) => {
return(
<TouchableOpacity onPress={() =>
this.removePhoto(photo.id)} key={Math.random()}>
<Image style={styles.image}
source={{ uri: photo.url }}
/>
</TouchableOpacity>
);
}) : null}
</View>
</ScrollView>
</SafeAreaView>
);
}
11. Finally, we'll add styles so that the app has a layout, as follows. There's
nothing here we haven't covered many times before:
const styles = StyleSheet.create({
container: {
backgroundColor: '#ecf0f1',
flex: 1,
},
toolbar: {
backgroundColor: '#3498db',
color: '#fff',
fontSize: 20,
textAlign: 'center',
padding: 20,
},
imageContainer: {
flex: 1,
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center',
},
image: {
height: 300,
width: 300
},
button: {
margin: 10,
padding: 20,
backgroundColor: '#3498db'
},
buttonText: {
fontSize: 18,
color: '#fff'
}
});
12. The app is complete! Clicking on the Add Photo button will add a new
photo to the beginning of the list of images, and pressing an image will
remove it. Note, since we are using a dummy data API, the POST and DELETE
requests will return proper responses for the given action. However, no data
is actually added or deleted to the database. This means that the image list
will reset if the app is refreshed, and that you can expect errors if you
attempt to delete any photos you've just added with the Add Photo button.
Feel free to connect this app to a real API and database to see the expected
results:
How it works...
In step 4, we used the connect method provided by react-redux to empower the Album
component with a connection to the Redux store we've been working on this
entire chapter. The call to connect returns a function that is immediately executed
via the second set of parentheses. By passing the Album component into this
returning function, connect glues the component and the store together.
In step 5, we defined the mapStateToProps function. The first parameter in this
function is state from the Redux store, which is injected into the function
by connect. Whatever keys are defined in the object returned from mapStateToProps
will be properties on the component's props. The value of these props will be
subscribed to state in the Redux store, so that any change affecting these pieces
of state will be automatically updated within the component.
While mapStateToProps will map state in the Redux store to the component
props, mapDispatchToProps will map the action creators to the component props. In
step 6, we defined this function. It has the special Redux method, dispatch,
injected into it for calling action creators that live in the store. mapDispatchToProps
returns an object, mapping the dispatch calls for actions to the components props
at the specified keys.
In step 7, we created the componentDidMount method. All the component needs to do
to get the photos it needs while mounting is to call the action creator mapped
to this.props.fetchPhotos. That's all! The fetchPhotos action creator will be
dispatched. The fetchPhoto action returned from the action creator will be
processed by the redux-promise-middleware we applied in a previous recipe since
the payload property of this action has a Promise stored on it in the form of an
axios AJAX request. The middleware will intercept the action, process the
request, and send a new action to the reducers with the resolved data on
the payload property. If it was a successful request, the action with
the FETCH_PHOTOS_FULFILLED type will be dispatched with the resolved data, and if
not, the FETCH_PHOTOS_REJECTED action will be dispatched with the error as payload. On
success, the case in the reducer for handling FETCH_PHOTOS_FULFILLED will
execute, loadedPhotos will be updated in the store, and in turn, this.props.photos will
also be updated. Updating the component props will trigger a re-render, and the
new data will be displayed on the screen.
In step 8 and step 9, we followed the same pattern to define addPhoto
and removePhoto, which call the action creators of the same name. The action
produced by the action creators are handled by the middleware, the proper
reducer handles the resulting action, and if the state in the Redux store changes,
all subscribed props will be automatically updated!
Storing offline content using Redux
Redux is an excellent tool for keeping track of an app's state while it it's running.
But what if we have data that we need to store without using an API? For
instance, we could save the state of a component so that when a user closes and
reopens the app, the previous state of that component can be restored, allowing
us to persist a piece of an app's persistent across sessions. Redux data persistence
could also be useful for caching information to avoid calling the API more than
necessary. You can refer to the Masking the application upon network
connection loss recipe in Chapter 8, Working with Application Logic and Data, for
more information on how to detect and handle network connectivity status.
Getting ready
This recipe depends on the previous ones, so make sure to follow along with all
of the previous recipes. In this recipe, we'll be using the redux-persist package to
persist the data in our app's Redux store. Install it with npm:
npm install --save redux--persist
Or you can install it with yarn:
yarn add redux--persist
How to do it...
1. Let's start by adding the dependencies we'll need in redux/index.js.
The storage method we're importing from redux-persist here will use React
Native's AsyncStorage method to store Redux data between sessions, as
follows:
import { persistStore, persistReducer } from 'redux-persist'
import storage from 'redux-persist/lib/storage';
2. We'll be using a simple config object for configuring our redux-persist
instance. config requires a key property for the key used to store the data with
AsyncStore and a storage property that takes the storage instance, as follows:
const persistConfig = {
key: 'root',
storage
}
3. We'll use the persistReducer method we imported in step 1. This method takes
the config object we created in step 2 as the first argument and our reducers
as the second:
const reducers = combineReducers({
photos,
});
const persistedReducer = persistReducer(persistConfig, reducers);
4. Now let's update our store to use the new persistedReducer method. Also note
how we no longer export store as the default export, since we'll need two
exports from this file:
export const store = createStore(persistedReducer, applyMiddleware(promiseMiddleware()));
5. The second export we need from this file is persistor. persistor will work to
persist the Redux store between sessions. We can create persistor by calling
the persistStore method and passing in store, as follows:
export const persistor = persistStore(store);
6. Now that we've got both store and persistor as exports from redux/index.js,
we're ready to apply them in App.js. We'll start by importing them, and we'll
import the PersistGate component from redux-persist. PersistGate will ensure
that our cached Redux store is loaded before any components are loaded:
import { PersistGate } from 'redux-persist/integration/react'
import { store, persistor } from './redux';
7. Let's update the App component to use PersistGate. The component takes two
props: the imported persistor prop and a loading prop. We'll be passing null to
the loading prop, but if we had a loading indicator component, we could pass
this in, and PersistGate would display this loading indicator as data is
restored, as follows:
const App = () => (
<Provider store={store}>
<PersistGate loading={null} persistor={persistor}>
<Album />
</PersistGate>
</Provider>
);
8. In order to test the persistence of our Redux store, let's adjust
the componentDidMount method in the Album component. We'll delay the call
to fetchPhotos for two seconds, so that we can see the saved data before it is
fetched again from the API, as follows:
componentDidMount() {
setTimeout(() => {
this.props.fetchPhotos();
}, 2000);
}
Depending on what kind of data you're persisting, this kind of functionality
could be applied to a number of situations, including persisting user data and app
state, even after the app's been closed. It can also be used to improve the offline
experience of an app, caching API requests if they can't be made right away and
providing users with data filled views.
How it works...
In step 2, we created the config object for configuring redux-persist. The object is
only required to have the key and store properties, but also supports quite a few
others. You can see all of the options this config takes via the type definition
hosted here: https://github.com/rt2zz/redux-persist/blob/master/src/types.js#L13-L27.
In step 7, we used the PersistGate component, which is how the documentation
recommends delaying rendering until restoring persisted data is complete. If we
have a loading indicator component, we can pass it to the loading prop for being
displayed while data is restored.
App Workflow and Third-Party
Plugins
This chapter works a bit differently, so we will first look into it before we go
ahead and cover the following recipes:
React Native development tools
Planning your app and choosing your workflow
Using NativeBase for cross-platform UI components
Using glamorous-native for styling UI components
Using react-native-spinkit for adding animated loading indicators
Using react-native-side-menu for adding side navigation menus
Using react-native-modalbox for adding modals
How this chapter works
In this chapter, we'll be taking a closer look at how each method of bootstrapping
a new React Native works, and how we can integrate third-party packages that
may or may not be Expo friendly. In previous chapters, the focus has been
entirely on building functional pieces of a React Native app. In this chapter,
many of these recipes will therefore also serve a secondary purpose of
illustrating how different packages can be implemented using different
workflows.
In each of the recipes in this chapter, we will begin with a pure React Native
project initialized with the React Native CLI command, which is done as
follows:
react-native init
Otherwise, we may do so with an app created via Expo.
When creating a new React Native app, you'll need to choose the right tooling
for initializing your app. Generally speaking, the tools you use for bootstrapping
and developing your React Native app will either focus on streamlining the
development process and purposefully obfuscating native code from you for the
sake of ease and mental overhead, or keep your development process flexible by
providing access to all native code and allowing the use of more third-party
plugins.
There are two methods for initializing and developing your app: Expo and the
React Native CLI. Until recently, there was a distinct third method, using Create
React Native App (CRNA). CRNA has since been merged with the Expo
project, and only continues to exist as a separate entity to provide backwards
compatibility.
Expo falls into the first category of tools, providing a more robust and
developer-friendly development workflow at the cost of some flexibility. Apps
bootstrapped with Expo also have access to a multitude of useful features
provided by the Expo SDK, such as BarcodeScanner, MapView, ImagePicker, and so
many more.
Initialize an app with the React Native CLI, via the following command:
react-native init
This provides flexibility at the cost of ease of development. A React Native app
is created with this command:
react-native init
It is said to be a pure React Native app, since none of the native code is hidden
away from the developer.
As a rule of thumb, the documentation for a third-party package states that you
need to run the following command:
react-native link
As it is part of the setup process, this package can only be used with a pure React
Native app.
So what do you do when you are halfway through building an app with Expo,
only to find out that a package integral to your app's requirements is not
supported by an Expo development workflow? Luckily, Expo has a method for
turning an Expo project into a pure React Native app, just as if it had been
created with the following command:
react-native init
When a project is ejected, all of the Native code is unpacked into ios and android
folders, and the App.js file is split into App.js and index.js, exposing the code that
mounts the root React Native component.
But what if your Expo app depends on features provided by the Expo SDK?
After all, much of the value of developing with Expo comes from the excellent
features the SDK provides, including AuthSession, Permissions, WebBrowser, and so
much more.
That’s where ExpoKit comes into play. When you choose to eject from a project,
you’re given the option of including ExpoKit as part of the ejected project.
Including ExpoKit will ensure that all of the Expo dependencies being used in
your app will continue to work, and also give you the ability to continue using
all the features of the Expo SDK, even after the app has been ejected.
For a deeper understanding of the eject processes, you can read the Expo
documentation at https://docs.expo.io/versions/latest/expokit/eject.
React Native development tools
As with any development tools, there is going to be a trade-off between
flexibility and ease of use. I encourage you start by using Expo for your React
Native development workflow, unless you’re sure you’ll need access to the
native code.
Expo
This was taken from the expo.io site:
"Expo is a free and open source toolchain built around React Native to help you build native iOS and
Android projects using JavaScript and React."
Expo is becoming an ecosystem of its own, and is made up of five
interconnected tools:
Expo CLI: The command-line interface for Expo. We'll be using the Expo
CLI to create, build, and serve apps. A list of all the commands supported
by the CLI can be found in the official documentation at the following link:
https://docs.expo.io/versions/latest/workflow/expo-cli
Expo developer tools: This is a browser-based tool that automatically runs
whenever an Expo app is started from the Terminal via the expo start
command. It provides active logs for your in-development app, and quick
access to running the app locally and sharing the app with other
developers.
Expo Client: An app for Android and iOS. This app allows you to run your
React Native project within the Expo app on the device, without the need
for installing it. This allows developers to hot reload on a real device, or
share development code with anyone else without the need for installing it.
Expo Snack: Hosted at https://snack.expo.io, this web app allows you to
work on a React Native app in the browser, with a live preview of the code
you’re working on. If you've ever used CodePen or JSFiddle, Snack is the
same concept applied to React Native applications.
Expo SDK: This is the SDK that houses a wonderful collection of
JavaScript APIs that provide Native functionality not found in the base
React Native package, including working with the device's accelerometer,
camera, notifications, geolocation, and many others. This SDK comes
baked in with every new project created with Expo.
These tools together make up the Expo workflow. With the Expo CLI, you can
create and build new applications with Expo SDK support baked in. The
XDE/CLI also provides a simple way to serve your in-development app by
automatically pushing your code to Amazon S3 and generating a URL for the
project. From there, the CLI generates a QR code linked to the hosted code.
Open the Expo Client app on your iPhone or Android device, scan the QR code,
and BOOM there’s your app, equipped with live/hot reload! And since the app is
hosted on Amazon S3, you can even share the in-development app with other
developers in real time.
React Native CLI
The original bootstrapping method for creating a new React Native app using
the command is as follows:
react-native init
This is provided by the React Native CLI. You'll likely only be using this method
of bootstrapping a new app if you're sure you'll need access to the native layer of
the app.
In the React Native community, an app created with this method is said to be a
pure React Native app, since all of the development and Native code files are
exposed to the developer. While this provides the most freedom, it also forces
the developer to maintain the native code. If you’re a JavaScript developer that’s
jumped onto the React Native bandwagon because you intend on writing native
applications solely with JavaScript, having to maintain the native code in a React
Native project is probably the biggest disadvantage of this method.
On the other hand, you'll have access to third-party plugins when working on an
app that's been bootstrapped with the following command:
react-native init
Get direct access to the native portion of the code base. You'll also be able to
sidestep a few of the limitations in Expo currently, particularly the inability to
use background audio or background GPS services.
CocoaPods
Once you begin working with apps that have components that use native code,
you're going to be using CocoaPods in your development as well. CocoaPods is
a dependency manager for Swift and Objective-C Cocoa projects. It works
nearly the same as npm, but manages open source dependencies for native iOS
code instead of JavaScript code.
We won't be using CocoaPods much in this book, but React Native makes use of
CocoaPods for some of its iOS integration, so having a basic understanding of
the manager can be helpful. Just as the package.json file houses all of the packages
for a JavaScript project managed with npm, CocoaPods uses a Podfile for listing a
project's iOS dependencies. Likewise, these dependencies can be installed using
the command:
pod install
Ruby is required for CocoaPods to run. Run the command at the command line
to verify Ruby is already installed:
ruby -v
If not, it can be installed with Homebrew with the command:
brew install ruby
Once Ruby has been installed, CocoaPods can be installed via the command:
sudo gem install cocoapods
If you encounter any issues while installing, you can read the official CocoaPods
Getting Started guide at https://guides.cocoapods.org/using/getting-started.html.
Planning your app and choosing your
workflow
When trying to choose which development workflow best fits your app's needs,
here are a few things you should consider:
Will I need access to the native portion of the code base?
Will I need any third-party packages in my app that are not supported by
Expo?
Will my app need to play audio while it is not in the foreground?
Will my app need location services while it is not in the foreground?
Will I need push notification support?
Am I comfortable working, at least nominally, in Xcode and Android
Studio?
In my experience, Expo usually serves as the best starting place. It provides a lot
of benefits to the development process, and gives you an escape hatch in the
eject process if your app grows beyond the original requirements. I would
recommend only starting development with the React Native CLI if you're sure
your app needs something that cannot be provided by an Expo app, or if you're
sure you will need to work on the Native code.
I also recommend browsing the Native Directory hosted at http://native.directory.
This site has a very large catalog of the third-party packages available for React
Native development. Each package listed on the site has an estimated stability,
popularity, and links to documentation. Arguably the best feature of the Native
Directory, however, is the ability to filter packages by what kind of
device/development they support, including iOS, Android, Expo, and web. This
will help you narrow down your package choices and better indicate which
workflow should be adopted for a given app.
How to do it...
We'll begin with the React Native CLI setup of our app, which will create a new
pure React Native app, giving us access to all of the Native code, but also
requiring that Xcode and Android Studio are installed.
You may recall from Chapter 1, Setting Up Your Environment, that some of these steps have
already been covered in detail. There is no need to reinstall anything listed here that was
described there as well.
1. First, we'll install all the dependencies needed for working with a pure
React Native app, starting with the Homebrew (https://brew.sh/) package
manager for macOS. As stated on the project's home page, Homebrew can
be easily installed from the Terminal via the following command:
/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
2. Once Homebrew is installed, it can be used to install the dependencies
needed for React Native development: Node.js and nodemon. If you're a
JavaScript developer, you've likely already got Node.js installed. You can
check it's installed via the following command:
node -v
This command will list the version of Node.js that's installed, if any. Note
that you will need Node.js version 8 or higher for React Native
development. If Node.js is not already installed, you can install it with
Hombrew via the following command:
brew install node
3. We also need the nodemon package, which React Native uses behind the
scenes to enable things like live reload during development. Install nodemon
with Homebrew via the following command:
brew install watchman
4. We'll also of course need the React Native CLI for running the commands
that bootstrap the React Native app. This can be installed globally with npm
via the following command:
npm install -g react-native-cli
5. With the CLI installed, all it takes to create a new pure React Native app is
the following:
react-native init name-of-project
This will create a new project in a new name-of-project directory. This
project has all Native code exposed, and requires Xcode for running the
iOS app and Android Studio for running the Android app. Luckily,
installing Xcode for supporting iOS React Native development is a
simple process. The first step is to download Xcode from the App Store
and install it. The second step is to install the Xcode command-line tools.
To do this, open Xcode, choose Preferences... from the Xcode menu,
open the Locations panel, and install the most recent version from the
Command Line Tools dropdown:
6. Unfortunately, setting up Android Studio for supporting Android React
Native development is not as cut and dry, and requires some very specific
steps for installing it. Since this process is particularly involved, and since
there is some likelihood that the process will have changed by the time you
read this chapter, I recommend referring to the official documentation for
in-depth, up-to-date instructions on installing all Android development
dependencies. These instructions are hosted at the following URL:
https://facebook.github.io/react-native/docs/getting-started.html#java-developme
nt-kit
7. Now that all dependencies have been installed, we're able to run our pure
React Native project via the command line. The iOS app can be executed
via the following:
react-native run-ios
And the Andriod app can be started with this:
react-native run-android
Each of these commands should start up the associated emulator for the
correct platform, install our new app, and run the app within the
emulator. If you have any trouble with either of these commands not
behaving as expected, you might be able to find an answer in the React
Native troubleshooting docs, hosted here:
https://facebook.github.io/react-native/docs/troubleshooting.html#content
Expo CLI setup
The Expo CLI can be installed using the Terminal with npm via the following
command:
npm install -g expo
The Expo CLI can be used to do all the great things the Expo GUI client can do.
For all the commands that can be run with the CLI, check out the docs here:
https://docs.expo.io/versions/latest/workflow/expo-cli
Using NativeBase for cross-platform
UI components
Similar to Bootstrap on the web, NativeBase is a collection of React Native
components for improving the efficiency of React Native app development. The
components cover a wide range of use cases for building out UI in Native
applications, including ActionSheets, Badges, Cards, Drawers, and grid layouts.
NativeBase is a library that supports both pure React Native applications (those
created with the React Native CLI via react-native init) and Expo powered
applications. Instructions for installing NativeBase into one type of project or
another is outlined in the Getting Started section of the NativeBase
documentation, hosted here:
https://github.com/GeekyAnts/NativeBase#4-getting-started
Since this is the case, we'll take this opportunity to outline both scenarios in
the Getting ready section of this recipe.
Getting ready
Whichever method of bootstrapping you use for this recipe, we'll be keeping
the How to do it... section of the recipe as consistent as possible. One difference
that we'll need to take into account is the project naming convention of each app
creation method. Pure React Native applications are named in Pascal case
(MyCoolApp) and Expo applications are named in kebab case (my-cool-app). If
you're creating a pure React Native app, you can use the app name NativeBase, and
if you're using Expo you can name it native-base.
Using a pure React Native app (React
Native CLI)
Assuming you've followed the introduction to this chapter, you should already
have the React Native CLI installed globally. If not, go ahead and do so now
with npm:
npm install -g react-native-cli
To create a new pure React app with the CLI, we'll use the following command:
react-native init NativeBase
This creates a new pure React Native app in a folder called NativeBase in the
current directory. The next step is to install the required peer dependencies.
Let's cd into the new NativeBase directory and install the native-base package using
npm:
npm install native-base --save
Alternatively, you can use yarn:
yarn add native-base
Finally, we will install the Native dependencies with the following command:
react-native link
If we open up the project in an IDE and look at the folder structure of this pure
React Native app, we'll see a few slight differences from the Expo applications
we've become accustomed to at this point. First, the repository has an ios and
an android folder, each containing Native code for the respective platform. There's
also an index.js file at the root of the project that is not included in an app
bootstrapped with Expo. In an app made with Expo, this file would be obscured
away, just like the ios and android folders, as follows:
import { AppRegistry } from 'react-native';
import App from './App';
AppRegistry.registerComponent('NativeBase', () => App);
This simply serves as the bootstrapping process of your React Native app at
runtime. AppRegistry is imported from the react-native packages, the main App
component is imported from the App.js file at the root of the directory, and
the AppRegistry method registerComponent is called with two parameters: the name of
our app (NativeBase), and an anonymous function that returns the App component.
For more information on AppRegistry, you can find the documentation here:
https://facebook.github.io/react-native/docs/appregistry.html
One other minor difference is the existence of two sets of development
instructions in the App.js boilerplate code, displaying the appropriate dev
instructions through the use of the Platform component.
Remember to stop and think whenever you see a third-party React Native
package whose installation instructions include running the following command:
react-native link
It is usually safe to assume it is not compatible with an Expo app unless
explicitly stated otherwise. In the case of NativeBase, we have an option to use
either setup, so let's cover getting started with our other option next,
bootstrapping with Expo.
Using an Expo app
Setting up Native Base in an app created with Expo is as simple as installing the
required dependencies with npm or yarn. First, we can create the app using
the Expo CLI on the command line:
expo init native-base
Once the app is created, we can cd into it and install the dependencies for
NativeBase with npm:
npm install native-base @expo/vector-icons --save
Alternatively, you can use yarn:
yarn add native-base @expo/vector-icons
When using NativeBase with Expo, the NativeBase documentation recommends
loading fonts asynchronously with the Expo.Font.loadAsync method in
the componentWillMount method in the App.js component. We'll cover how to do this
in the appropriate step in the How to do it... section of this recipe. You can start
up the app from the CLI with the following command:
expo start
How to do it...
1. We'll start by adding the imports we'll be using in the App component in
App.js. While this app won't have much functionality, we will be using a
number of components from NativeBase to see how they can help improve
your workflow, as follows:
import React, { Component } from 'react';
import { View, Text, StyleSheet } from 'react-native'
import {
Spinner,
Button,
Body,
Title,
Container,
Header,
Fab,
Icon,
} from 'native-base';
2. Next, let's declare the App class and define a starting state object. We'll be
adding a FAB section to show how NativeBase lets you easily add fly-out
menu buttons to your app. We will track whether this menu should be
displayed or not with the fabActive Boolean. We'll also use the loading
Boolean later in the render method, as follows:
export default class App extends Component {
state = {
loading: true
fabActive: false
}
// Defined on following steps
}
3. You may recall from the Getting ready section of the recipe, if you're
developing an app with Expo, NativeBase suggests loading the fonts used
by NativeBase via the Expo.Font.loadAsync function. In the componentWillMount
method, we'll initialize and await the loading of require fonts, then set
the loading property on state to false. The loading property will be referenced
in the render method to determine whether the app has finished loading, as
follows:
// Other import statements
import { Font, AppLoaded } from 'expo';
export default class App extends Component {
state = {
fabActive: false
}
async componentWillMount() {
await Font.loadAsync({
'Roboto': require('native-base/Fonts/Roboto.ttf'),
'Roboto_medium': require('native-base/Fonts/Roboto_medium.ttf'),
'Ionicons': require('@expo/vector-icons/fonts/Ionicons.ttf'),
});
this.setState({ loading: false });
}
// Defined on following steps
}
4. Since this app is mostly UI, we're ready to start building the render function.
To make sure fonts are loaded before we use them, we return the App
placeholder Expo component, AppLoading, if the loading property of state is
true, otherwise we'll render the App UI. AppLoading will instruct the app to
continue displaying the app's splash screen until the component is removed.
If you chose to start this recipe with a pure React Native project, you won't have
access to Expo components. You can simply return an empty View instead of AppLoading in
this case.
5. We'll start with the Container component, along with the Header, Body, and Title
helper components. This will act as the container for the page, displaying a
header at the top of the page with the title Header Title!
render() {
if (this.state.loading) {
return <AppLoading />;
} else {
return (
<Container>
<Header>
<Body>
<Title>Header Title!</Title>
</Body>
</Header>
</Container>
);
}
}
At this point, the app should look similar to the following screenshot:
6. In the following code, the Header will have a few more UI elements from
NativeBase. The Spinner component allows for easily displaying a loading
spinner with the desired color passed in as a prop. The Button component
provides buttons with more built-in customizability when compared with
the vanilla TouchableOpacity component. Here, we're using the block prop to
spread the buttons across their container, and an info and success prop on
each to apply their respective default blue and green background colors:
<Container>
<Header>
<Body>
<Title>Header Title!</Title>
</Body>
</Header>
<View style={styles.view}>
<Spinner color='green' style={styles.spinner} />
<Button block info
onPress={() => { console.log('button 1 pressed') }}
>
<Text style={styles.buttonText}>Click Me! </Text>
</Button>
<Button block success
onPress={() => { console.log('button 2 pressed') }}
>
<Text style={styles.buttonText}>No Click Me!</Text>
</Button>
{this.renderFab()}
</View>
</Container>
7. The preceding render function also refers to a renderFab method we have not
yet defined. This makes use of the Icon and Fab components. NativeBase
uses the same vector-icons package as Expo under the hood (defaulting to
Ionicon fonts if no type prop is provided), which was covered in the Using
Font icons recipe in Chapter 3, Implementing Complex User Interfaces – Part
I, so please refer to that recipe for more information:
renderFab = () => {
return (
<Fab active={this.state.fabActive}
direction="up"
style={styles.fab}
position="bottomRight"
onPress={() => this.setState({ fabActive:
!this.state.fabActive })}>
<Icon name="share" />
<Button style={styles.facebookButton}
onPress={() => { console.log('facebook button pressed') }}
>
<Icon name="logo-facebook" />
</Button>
<Button style={styles.twitterButton}
onPress={() => { console.log('twitter button pressed')}}
>
<Icon name="logo-twitter" />
</Button>
</Fab>
);
}
8. Let's round this recipe out with a few styles to align things within the View
and apply colors to our layout, as follows:
const styles = StyleSheet.create({
view: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
paddingBottom: 40
},
buttonText: {
color: '#fff'
},
fab: {
backgroundColor: '#007AFF'
},
twitterButton: {
backgroundColor: '#1DA1F2'
},
facebookButton: {
backgroundColor: '#3B5998'
},
spinner: {
marginBottom: 180
}
});
9. Looking back at the completed app, there's now a nice spread of UI that is
cross-platform and easy to use:
How it works...
While the more complicated portion of this recipe was the set-up of the app
itself, we had a quick review of a few of the components provided by
NativeBase that might be able to help you develop your next app more
efficiently. If you prefer to work in a widget-based system similar to what
Bootstrap (https://getbootstrap.com/) or Semantic-UI (https://semantic-ui.com/)
provide on the web platform, be sure to give NativeBase a spin. For more
information on all of the components that NativeBase offers and how to use
them, you can find the official documentation at http://docs.nativebase.io/Components
.html.
Using glamorous-native for styling UI
components
As a JavaScript developer, you're likely familiar with CSS on the web and how
it's used to style web pages and web applications. More recently, a technique
called CSS-in-JS has came along in web development, which uses the power of
JavaScript to adapt CSS for a more modular, component-based styling approach.
One of the main benefits of CSS-in-JS tools is their ability to produce styles that
are scoped to a given element, instead of the default cascading behavior of
vanilla JavaScript. Scoped CSS allows a developer to apply styles in a more
predictable and modular way. This in turn increases usability in larger
organizations and makes packaging and publishing styled components easier. If
you'd like to learn more about how CSS-in-JS works or where CSS-in-JS comes
from conceptually, I've written an article on the topic on the gitconnected
Medium blog called A Brief History of CSS-in-JS: How We Got Here and Where
We're Going, hosted at:
https://levelup.gitconnected.com/a-brief-history-of-css-in-js-how-we-got-here-and-where-we
re-going-ea6261c19f04.
The StyleSheet component that comes packaged with React Native is an
implementation of CSS-in-JS. One of the most popular implementations of CSS-
in-JS on the web is glamorous, a library created by the venerable Kent C. Dodds.
This library inspired the excellent React Native styling library glamorous-native,
which we will be using in this recipe.
Getting ready
We'll need to create a new app for this recipe. This package does not require
running the following command during setup:
react-native link
So, should work just fine with an Expo app. Let's name the recipe glamorous-app.
We will also need to install the glamorous-app package. This can be installed
with npm:
npm install --save glamorous-native
Or, we can use yarn:
yarn add glamorous-native
How to do it...
1. Let's start by importing all the dependencies we'll need in App.js, as follows:
import React from 'react';
import glamorous from 'glamorous-native';
2. Our app will need a containing View element to hold all of the other
components displayed in the app. Instead of styling this element with an
object passed to the StyleSheet component, like we've been doing in all
previous recipes, we'll use glamorous by passing a style object to the view
method, which returns a styled View component that we store in a const
called Container for later use, as follows:
const Container = glamorous.view({
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#fff',
});
3. Similarly, we'll add three styled Text components using glamorous.text. By
doing this, we have three more styled and explicitly named components
ready to be used in render, as follows:
const Headline = glamorous.text({
fontSize: 30,
paddingBottom: 8
});
const SubHeading = glamorous.text({
fontSize: 26,
paddingBottom: 8
});
const ButtonText = glamorous.text({
fontSize: 18,
color: 'white'
});
4. We'll also make a reusable Button component with
the glamorous.touchableHighlight method. This method shows how glamorous
components can also be created with multiple style declarations of different
types. The second parameter passed to touchableHighlight in this case is a
function that updates the backgroundColor style depending on the props defined
on the element, as follows:
const Button = glamorous.touchableHighlight(
{ padding: 10 },
props => ({backgroundColor: props.warning ? 'red' : 'blue'})
);
5. We can also create components styled inline, thanks to the special versions
of React Native components glamorous ships with. We will use an Image
component, but instead of importing it from react-native, we use the Image
component from the imported glamorous package, as follows:
const { Image } = glamorous;
6. Now, we are ready to declare the App component. App will only need a render
function for rendering all our newly styled components, as follows:
export default class App extends React.Component {
render() {
// Defined in following steps.
}
}
7. Let's begin building out the render function by adding the Container
component we created in step 2. The improvement in code readability is
already apparent. The Container is explicitly named and needs no other
attributes or properties to declare styles, as follows:
render() {
return (
<Container>
// Defined on following steps
</Container>
);
}
8. Let's add the Image component that we pulled from the imported glamorous
library in step 5. Notice how we are able to declare style properties such
as height, width, and borderRadius as props directly on the component, unlike
the vanilla Image component:
<Container>
<Image
height={250}
width={250}
borderRadius={20}
source={{ uri: 'http://placehold.it/250/3B5998' }}
/>
// Defined on following steps
</Container>
9. Now, we'll add the Headline and Subheading components we created in step 3.
Just like the Container component, these two components read much more
clearly than one View and two Text elements ever could:
<Container>
<Image
height={250}
width={250}
borderRadius={20}
source={{ uri: 'http://placehold.it/250/3B5998' }}
/>
<Headline>I am a headline</Headline>
<SubHeading>I am a subheading</SubHeading>
// Defined in following steps
<Container>
10. Finally, we'll add the Button component we created in step 4, and the
ButtonText component we created in step 3. Both buttons have an onPress
method like any TouchableOpacity or TouchableHighlight component would, but
the second Button also has a warning prop, causing it to have a red background
instead of blue:
<Button
onPress={() => console.log('Thanks for clicking me!')}
>
<ButtonText>
Click Me!
</ButtonText>
</Button>
<Button
warning
onPress={() => console.log(`You shouldn't have clicked me!`)}
>
<ButtonText>
Don't Click Me!
</ButtonText>
</Button>
11. All of our glamorous components have been added to the render method. If you
run the app, you should be greeted by a fully styled UI.
How it works...
In step 2 and step 3, we created styled View and Text components by using the
corresponding glamorous method and passing in an object containing all the styles
that should be applied to that particular component.
In step 4, we created a reusable Button styled component by applying the same
method used for creating the View and Text components in previous steps. The
way styles are declared in this component is different, however, and shows off
the versatility glamorous-native has when processing styles. You can pass any
number of style collections as parameters to a glamorous component constructor
and they will all be applied. This includes dynamic styles, which usually take the
form of using props defined on the component to apply different styles. In step
10, we used our Button element. If the prop warning is present, as it is on the first
Button in render, the backgroundColor will be red. Otherwise, it will be blue. This
provides a very nice system for applying simple and reusable theming across
multiple types of components.
In step 5, we pulled the Image component from the glamorous library to use in place
of the React Native Image component. This special version of the component
behaves the same as its React Native counterpart, along with the benefit of being
able to apply styles directly to the element itself. In step 8, where we used that
component, we were able to apply height, width, and borderRadius styles without
ever having to use the style prop.
Using react-native-spinkit for adding
animated loading indicators
No matter what kind of app you are building, there's a very good chance your
app will need to wait on data of one kind or another, whether it be loading assets
or waiting on a response from an AJAX request. When this situation arises,
you'll probably also want a way for your app to indicate to the user that some
required piece of data is still loading. One easy-to-use solution to this problem is
using react-native-spinkit. This package provides 15 (four of which are iOS-only)
professional looking, easy-to-use loading indicators for displaying while data is
loading in your app.
This package requires the following command to be run:
react-native link
So, i is probably safe to assume that it will not work with an Expo app (unless
that app is subsequently ejected/detached). This will provide us with another
recipe that depends on a pure React Native workflow.
Getting started
Now that we've established that this recipe will be built in pure React Native, we
can begin by initializing a new app from the command line named SpinKitApp as
follows:
react-native init SpinKitApp
This command will begin the scaffolding process. Once it has completed, cd into
the new SpinKitApp directory and add react-native spinkit with npm:
npm install react-native-spinkit@latest --save
Or use yarn:
yarn add react-native-spinkit@latest
With the library installed, we must link it before it can be used with the
command:
react-native link
At this point, the app is bootstrapped and the dependencies have been installed.
The app can then be run in the iOS or Android simulators via this:
react-native run-ios
Or, use this:
react-native run-android
When launching a pure React Native project in the iOS simulator, if you wish to specify a
device, you can pass the simulator argument set to a string value for the desired device. For
example, react-native run-ios --simulator="iPhone X" will launch the app in a simulated iPhone X.
When launching a pure React Native project in an Android emulator via the command line,
you must open the Android emulator you intend to use before running this command.
We'll also be making use of the randomcolor library again in this recipe. Install it
with npm:
npm install randomcolor --save
Or use yarn:
yarn add randomcolor
How to do it...
1. We'll start by adding the dependencies to the App.js file in the root of the
project, as follows:
import React, { Component } from 'react';
import {
StyleSheet,
View,
TouchableOpacity,
Text
} from 'react-native';
import Spinner from 'react-native-spinkit';
import randomColor from 'randomcolor';
2. We're going to be setting up the app in this recipe to cycle through all of the
loading spinner types provided by react-native-spinkit. To do this, let's create
an array with strings for each possible type of spinner. Since the last four
types are not fully supported in Android, they will all appear as the same
Plane spinner on Android, as follows:
const types = [
'Bounce',
'Wave',
'WanderingCubes',
'Pulse',
'ChasingDots',
'ThreeBounce',
'Circle',
'9CubeGrid',
'FadingCircleAlt',
'FadingCircle',
'CircleFlip',
'WordPress',
'Arc',
'ArcAlt'
];
3. Now, we can begin building the App component. We will need a state object
with four properties: an isVisible property to track whether the spinner
should be displayed, a type property for holding the current spinner type, a
typeIndex for keeping our place in the types array, and a color. We'll initialize
color to a random hex code by simply calling randomColor(), as follows:
export default class App extends Component {
state = {
isVisible: true,
typeIndex: 0,
type: types[0],
color: randomColor()
}
}
4. We'll need a function for changing the properties of the Spinner component,
which we will define later in the render method. This function simply
increases the typeIndex by one, or sets it back to 0 if the end of the array has
been reached, then updates state accordingly, as follows:
changeSpinner = () => {
const { typeIndex } = this.state;
let nextType = typeIndex === types.length - 1 ? 0 : typeIndex +
1;
this.setState({
color: randomColor(),
typeIndex: nextType,
type: types[nextType]
});
}
5. The render method will be made up of the Spinner component, wrapped in
a TouchableOpacity component for changing the type and color of Spinner. We
will also add a Text component for displaying the current Spinner type, as
follows:
render() {
return (
<View style={styles.container}>
<TouchableOpacity onPress={this.changeSpinner}>
<Spinner
isVisible={this.state.isVisible}
size={120}
type={this.state.type}
color={this.state.color}
/>
</TouchableOpacity>
<Text style={styles.text}>{this.state.type}</Text>
</View>
);
}
6. Finally, let's add a few styles to the center content and increase the font size
of the Text element via the text class, as follows:
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#fff',
},
text: {
paddingTop: 40,
fontSize: 25
}
});
7. With the recipe complete, we should see a loader that changes on press.
Thanks to react-native-spinkit, this is all it takes to add slick loading
indicators to our React Native applications!
How it works...
In step 5, we defined the app's render method, where we made use of the Spinner
component. The Spinner component has four optional props:
isVisible: A Boolean that determines whether the component should be
displayed. Default: true
color: A hex code to determine the spinner's color. Default: #000000
size: Determines what size the spinner should be, in pixels. Default: 37
type: A string that determines the type of spinner to use. Default: Plane
Since the isVisible prop on the Spinner component is set to the value of isVisible on
the state object, we can simply toggle this property to true whenever a long
running process begins (such as waiting on the response from an AJAX request),
and set it back to false when the operation completes.
There's more...
Even though the app we've created in this recipe is fairly simple, it has illustrated
both how react-native-spinkit can be implemented, and how using third-party
packages that require the react-native link command works in practice. There are
all kinds of third-party packages available to use in your next React Native app,
thanks to the hard work of countless open source contributors. Being equipped to
utilize any third-party package that suits your app's needs, no matter what
requirements those package have, will be a vital tool in planning and developing
React Native projects.
Using react-native-side-menu for
adding side navigation menus
Side menus are a common UX pattern for displaying options, controls, app
settings, navigation, and other secondary information in mobile applications.
The react-native-side-menu third-party package provides an excellent,
straightforward way to implement side menus in a React Native app. In this
recipe, we will be building an app that has a side menu housing buttons that
change the background.
Getting ready
Setting up the react-native-side-menu package does not require the command:
react-native link
So feel free to create this app with Expo or as a pure React Native app. We need
to create a new app for this recipe, and for project naming purposes we'll assume
this app is being built with Expo and name it side-menu-app. If you're using pure
React Native, you can name it SideMenuApp.
We will also need to install react-native-side-menu into our project with npm:
npm install react-native-side-menu --save
Or, use yarn:
yarn add react-native-side-menu
How to do it...
1. Let's start this recipe by adding all the imports we'll need in the App.js file in
the root of the project. One of these imports is a Menu component, which
we'll create in a later step:
import React from 'react';
import { StyleSheet, Text, View, TouchableOpacity } from 'react-native';
import SideMenu from 'react-native-side-menu';
import Menu from './components/Menu';
2. Next, let's define the App class and the initial state. state only needs two
properties in this app: an isOpen Boolean to keep track of when the side
menu should be open, and a selectedBackgroundColor property whose value is a
string representing the currently selected background color, as follows:
export default class App extends React.Component {
state = {
isOpen: false,
selectedBackgroundColor: 'green'
}
// Defined in following steps
}
3. Our app will need a method for changing the selectedBackgroundColor property
on state. This method takes a color string as a parameter, and sets that color
to selectedBackgroundColor. It will also set state.isOpen to false so that the side
menu closes when a color is selected from the menu, as follows:
changeBackgroundColor = color => {
this.setState({
isOpen: false,
selectedBackgroundColor: color,
});
}
4. We're ready to define the render method App. First, let's set up the Menu
component so it can be used by SideMenu in the next step. We still haven't
created the Menu component, but we'll be using an onColorSelected property to
pass along the changeBackgroundColor method, as follows:
render() {
const menu = <Menu onColorSelected={this.changeBackgroundColor}
/>;
// Defined in next step
}
5. The rendered UI consists of four pieces. The first is a View component,
which has a style property tied to state.selectedBackgroundColor. This View
component holds a single TouchableOpacity button component, which opens
the side menu whenever it's pressed. The SideMenu component has a required
menu prop, which takes the component that will act as the side menu itself,
and so we'll pass the Menu component to this property, as follows:
render() {
const menu = <Menu onColorSelected={this.changeBackgroundColor} />;
return (
<SideMenu
menu={menu}
isOpen={this.state.isOpen}
onChange={(isOpen) => this.setState({ isOpen })}
>
<View style={[
styles.container,
{ backgroundColor: this.state.selectedBackgroundColor }
]}>
<TouchableOpacity
style={styles.button}
onPress={() => this.setState({ isOpen: true })}
>
<Text style={styles.buttonText}>Open Menu</Text>
</TouchableOpacity>
</View>
</SideMenu>
);
}
6. As the final touch for this component, let's add basic styles to center the
layout, and apply colors and font sizes, as follows:
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
},
button: {
backgroundColor: 'black',
padding: 20,
borderRadius: 10
},
buttonText: {
color: 'white',
fontSize: 25
}
});
7. It's time to create the Menu component. Let's create a component folder with
a Menu.js file inside. We'll start with the component imports. As we've done
in previous recipes, we'll also use Dimensions to store the dimensions of the
app window in a variable for applying styles, as follows:
import React from 'react';
import {
Dimensions,
StyleSheet,
View,
Text,
TouchableOpacity
} from 'react-native';
const window = Dimensions.get('window');
8. The Menu component needs only to be a presentational component, since it
has no state or need for life cycle hooks. The component will receive
onColorSelected as a property, which we'll make use of in the next step, as
follows:
const Menu = ({ onColorSelected }) => {
return (
// Defined on next step
);
}
export default Menu;
9. The body of the Menu component is simply a list of TouchableOpacity buttons
that, when pressed, call onColorSelected, passing in the corresponding color,
as follows:
<View style={styles.menu}>
<Text style={styles.heading}>Select a Color</Text>
<TouchableOpacity onPress={() => onColorSelected('green')}>
<Text style={styles.item}>
Green
</Text>
</TouchableOpacity>
<TouchableOpacity onPress={() => onColorSelected('blue')}>
<Text style={styles.item}>
Blue
</Text>
</TouchableOpacity>
<TouchableOpacity onPress={() => onColorSelected('orange')}>
<Text style={styles.item}>
Orange
</Text>
</TouchableOpacity>
<TouchableOpacity onPress={() => onColorSelected('pink')}>
<Text style={styles.item}>
Pink
</Text>
</TouchableOpacity>
<TouchableOpacity onPress={() => onColorSelected('cyan')}>
<Text style={styles.item}>
Cyan
</Text>
</TouchableOpacity>
<TouchableOpacity onPress={() => onColorSelected('yellow')}>
<Text style={styles.item}>
Yellow
</Text>
</TouchableOpacity>
<TouchableOpacity onPress={() => onColorSelected('purple')}>
<Text style={styles.item}>
Purple
</Text>
</TouchableOpacity>
</View>
10. Let's add a few styles to layout the Menu component, apply colors, and apply
font sizes. Note that we're also using the window variable we defined in step 7
to set the height and width of the component equal to that of the screen, as
follows:
const styles = StyleSheet.create({
menu: {
flex: 1,
width: window.width,
height: window.height,
backgroundColor: '#3C3C3C',
justifyContent: 'center',
padding: 20,
},
heading: {
fontSize: 22,
color: '#f6f6f6',
fontWeight: 'bold',
paddingBottom: 20
},
item: {
fontSize: 25,
paddingTop: 10,
color: '#f6f6f6'
}
});
11. Our app is complete! When the Open Menu button is pressed, a smoothly
animated side menu will slide out from the left, displaying a list of colors
for the user to choose from. When a color is selected from the list, the
background color of the app changes and the menu slides back to closed:
How it works...
In step 4, we created the render function for the main App component. We stored
the Menu component in a menu variable so that it can be legibly passed to the menu
property of SideMenu, as we did in step 5. We pass the changeBackgroundColor
class method via the onColorSelected prop on our Menu component so that we can
use it to properly update state in the App component.
We then pass the Menu component to SideMenu as the menu prop, which wires the two
components together. The second props is isOpen, which dictates whether the side
menu should be open. The third prop, onChange, takes a callback function that's
executed every time the menu is opened or closed. The onChange callback provided
an isOpen parameter that we used to update the value of isOpen on state so that it
stays in sync.
The containing View element has a style prop set to an array with both the container
styles defined in step 6 and an object with the backgroundColor key set
to selectedBackgroundColor on state. This will cause the background color of the View
component to change to this value whenever it updates.
In step 8 and step 9, we built out the render method of the Menu component.
Each TouchableOpacity button is wired to call onColorSelected, passing in the color
associated with the pressed button. This in turn runs changeBackgroundColor in the
parent App class, which updates state.selectedBackgroundColor on setting state.isOpen
to false, causing the background color to change and the side menu to close.
Using react-native-modalbox for
adding modals
Another common piece of many mobile UIs is the modal. Modals are the perfect
solution for isolating data in a meaningful way, alerting a user of updated info,
displaying a required action that blocks other user interactions (like a login
screen), and so much more.
We will be making use of the third-party package react-native-modalbox. This
package provides an easy-to-understand and versatile API for creating modals,
with options including the following:
position: Top, bottom, center
entry: Direction modal enters from—top or bottom?
backdropColor
backdropOpacity
For all of the available options, refer to the documentation at:
https://github.com/maxs15/react-native-modalbox
Getting ready
We will need a new app for this recipe. The react-native-modalbox package is Expo
friendly, so we can create this app with Expo. We'll name this app modal-app. If
using a pure React Native project, a name such as ModalApp will work, to match
naming conventions.
We will also need the third-party package. It can be installed with npm:
npm install react-native-modalbox --save
Or, use yarn:
yarn add react-native-modalbox
How to do it...
1. Let's start by opening the App.js file in the root of the project and add the
imports, as follows:
import React from 'react';
import Modal from 'react-native-modalbox';
import {
Text,
StyleSheet,
View,
TouchableOpacity
} from 'react-native';
2. Next, we will define and export the App component, as well as the initial
state object, as follows. For this app, we'll only need an isOpen Boolean for
keeping track of whether one of our modals should be opened or closed:
export default class App extends Component {
state = {
isOpen: false
};
// Defined on following steps
}
3. Let's skip ahead to building out the render method next. The template is
made up of two TouchableOpacity button components that when pressed, open
their respective modal. We'll be defining those two modals in the following
steps. These buttons will call two methods for rendering each Modal of the
two modal components, as follows:
render = () => {
return (
<View style={styles.container}>
<TouchableOpacity
onPress={this.openModal1}
style={styles.button}
>
<Text style={styles.buttonText}>
Open Modal 1
</Text>
</TouchableOpacity>
<TouchableOpacity
onPress={this.openModal2}
style={styles.button}
>
<Text style={styles.buttonText}>
Open Modal 2
</Text>
</TouchableOpacity>
{this.renderModal1()}
{this.renderModal2()}
</View>
);
}
4. Now, we're ready to define the renderModal1 method. The Modal component
needs a ref prop to be assigned a string, which will be used to refer to the
Modal when we want to open or close it, as follows:
renderModal1 = () => {
return(
<Modal
style={[styles.modal, styles.modal1]}
ref={'modal1'}
onClosed={this.onClose}
onOpened={this.onOpen}
>
<Text style={styles.modalText}>
Hello from Modal 1
</Text>
</Modal>
)
}
5. Let's add the openModal1 method next. This is the method that is called
by onPress on the first TouchableOpacity component we added in the render
method in step 3. By passing the modal1 string to the ref prop on the Modal
component we defined in step 4, we're able to access the modal
as this.refs.modal1. Calling the open method on this ref opens the modal. More
on this in the How it works... section at the end of this recipe. Add
the openModal1 method as follows:
openModal1 = () => {
this.refs.modal1.open();
}
6. The Modal we defined in step 4 also has onClosed and onOpened props, which
each take a callback that's executed when the modal is closed or opened,
respectively. Let's define the callbacks for these props next. In this recipe,
we'll just be firing a console.log as a proof of concept, as follows:
onClose = () => {
console.log('modal is closed');
}
onOpen = () => {
console.log('modal is open');
}
7. We're ready to define the second modal. This Modal component's ref prop
will be set to the string modal2, and we'll add two other optional props we
didn't use on the other modal. The first is position, which can be set
to top, bottom, or center (default). The isOpen prop provides a secondary way of
opening and closing a modal via a Boolean. The content of the modal has
a TouchableOpacity with an OK button that, when pressed, will set the isOpen
Boolean on the state object to false, closing the modal, as follows:
renderModal2 = () => {
return(
<Modal
style={[styles.modal, styles.modal2]}
ref={'modal2'}
position={'bottom'}
onClosed={this.onCloseModal2}
isOpen={this.state.isOpen}
>
<Text style={styles.modalText}>
Hello from Modal 2
</Text>
<TouchableOpacity
onPress={() => this.setState({isOpen: false})}
style={styles.button}
>
<Text style={styles.buttonText}>
OK
</Text>
</TouchableOpacity>
</Modal>
)
}
8. Since we're using the state Boolean isOpen to manipulate the state of the
modal, the openModal2 method will illustrate an alternative method for
opening and closing the modal. By setting isOpen on state to true, the second
modal will open, as follows:
openModal2 = () => {
this.setState({ isOpen: true });
}
9. You might have also noticed that the second modal, defined in step 7, has a
different onClose callback. If the user presses the OK button, the isOpen value
on state will be successfully updated to false, but if they dismiss the modal
by touching the backdrop, it will not. This method guarantees that the isOpen
value of the state is properly kept in sync no matter how the user dismisses
the modal, as follows:
onCloseModal2 = () => {
this.setState({ isOpen: false });
}
10. The last step in this recipe is applying styles. We'll have a modal class for
shared modal styles, modal1 and modal2 classes for styles unique to each
modal, and classes for applying colors, padding, and margin to buttons and
text, as follows:
const styles = StyleSheet.create({
container: {
backgroundColor: '#f6f6f6',
justifyContent: 'center',
alignItems: 'center',
flex: 1
},
modal: {
width: 300,
justifyContent: 'center',
alignItems: 'center'
},
modal1: {
height: 200,
backgroundColor: "#4AC9B0"
},
modal2: {
height: 300,
backgroundColor: "#6CCEFF"
},
modalText: {
fontSize: 25,
padding: 10,
color: '#474747'
},
button: {
backgroundColor: '#000',
padding: 16,
borderRadius: 10,
marginTop: 20
},
buttonText: {
fontSize: 30,
color: '#fff'
}
});
11. This recipe is complete, and we now have an app with two basic modals,
displayed on button press, and living in harmony in the same component:
How it works...
In step 4, we defined the first Modal component. We defined the onClosed
and onOpened props, passing the onClose and onOpen class methods to these props.
Whenever this Modal component is opened, this.onOpen will fire, and this.onClose
will execute when the Modal is closed. While we didn't do anything exciting with
these methods in this recipe, these hooks could serve as the perfect opportunity
for logging user actions related to the modal. Or if the modal houses a form,
onOpen could be used to pre-populate some form inputs with data, and onClose
could save the form data to the state object for use as the modal is closed.
In step 5, we defined the method that the first TouchableOpacity button component
executes when pressed: openModal1. In this method, we made use of the Modal
components ref. Refs are a core feature of React itself, and provide a place on
the component instance for storing DOM nodes and/or React elements that are
created in the component's render method. Just as React (and React Native)
components have both state and props (this.state, and this.props in a class
component), they can also have refs (which live on this.ref). For more on how
refs in React work, check the documentation at:
https://reactjs.org/docs/refs-and-the-dom.html
Since we set the ref prop on the first Modal to the string modal1, we're able to access
this same component in the openModal1 method with the reference this.ref.modal1.
Since Modal has an open and a close method, calling this.ref.modal1.open() opens
the Modal with a ref of modal1.
This is not the only way to open and close a Modal component, as illustrated with
the second modal we defined in step 7. Since this component has an isOpen prop,
the modal can be opened or closed by changing the Boolean value being passed
to the prop. By setting isOpen to be the isOpen value of the state, we can use the
OK button in this modal to close the modal from within, by setting isOpen to false
on state. In step 8, we defined the openModal2 method, which also illustrates
opening the second modal by changing the value of isOpen on state.
In step 9, we defined a separate isClosed callback for keeping the isOpen value of
state in sync in case the user dismisses the modal by pressing the backdrop
instead of the modal's OK button. An alternative strategy would have been to
disable the user's ability to dismiss the modal via pressing the backdrop, by
adding the backdropPressToClose property to the Modal component and setting it to
false.
There are a number of other optional props provided by the react-native-modalbox
package that can make modal creation easier. We used position in this recipe to
declare that the second modal be placed at the bottom of the screen, and you can
view all other available props for Modal in the documentation at:
https://github.com/maxs15/react-native-modalbox
The react-native-modalbox library supports multiple modals in a single component; however,
attempting to use the isOpen prop on more than one of these modals will cause all of those
modals to open at once, which is unlikely to be the desired behavior.
Adding Native Functionality - Part I
In this chapter, we'll cover the following recipes:
Exposing custom iOS modules
Rendering custom iOS view components
Exposing custom Android modules
Rendering custom Android view components
Handling the Android back button
Introduction
One of the core principles in React Native development is writing JavaScript to
build truly native mobile applications. To accomplish this, many native APIs and
UI components are exposed through an abstraction layer and are accessed
through the React Native bridge. While the React Native and Expo teams
continue to improve and expand on the already impressive APIs that currently
exist, through the native APIs, we can access functionality that isn't available
otherwise, such as vibration, contacts, and native alerts and toasts.
By exposing the native view components, we're able to leverage all of the
rendering performance the device has to offer, as we're not going through a
WebView as in a hybrid app. This gives a native look and feel that adapts to the
platform the user is running the app on. With React Native, we're already able to
render many native view components including maps, lists, input fields, toolbars,
and pickers.
While React Native comes with many built-in native modules and view
components, we're often in a position where we need some custom functionality
leveraging the native application layer that isn't provided out of the box.
Fortunately, there's an extremely rich open source community supporting React
Native that not only contributes to the library itself, but also publishes libraries
that export some common native modules and view components. If you can't
find a first- or third-party library to accomplish what you need, you can always
build it yourself.
In this chapter, we'll cover recipes that go over exposing custom native
functionality, whether it's an API or view component, on both platforms.
There will be a lot of generated code in the native portions of the code we'll be using in these
recipes. The code blocks provided throughout this chapter will, like in previous chapters,
continue to display all of the code used in a particular step, whether it's added by us or
generated, unless stated otherwise. This is intended to ease the burden of understanding the
context of a piece of code, and facilitates the discussion of these pieces of generated code
when further explanation is warranted.
Exposing custom iOS modules
As you begin developing more interesting and complex React Native
applications, you could possibly reach a point where executing certain code
would be only possible (or significantly improved) in the native layer. This
allows for executing data processing that's faster in the native layer when
compared with JavaScript, and for accessing certain native functionality that isn't
otherwise exposed, such as file I/O, or leveraging existing native code from
other applications or libraries in your React Native app.
This recipe will walk you through the process of executing some native
Objective-C or Swift code and communicating with the JavaScript layer. We'll
build a native HelloManager module that will greet our user with a message. We'll
also show how to execute native Objective-C and Swift code, taking in
arguments, and showing several ways of communicating back with the UI (or
JavaScript) layer.
Getting ready
For this recipe, we'll need a new empty, pure React Native application. Let's call
it NativeModuleApp.
In this recipe, we'll also make use of the react-native-button library. This library
will allow us to work with a Button component that's more sophisticated than the
React Native counterparts. It can be installed with npm:
npm install react-native-button --save
Or it can be installed using yarn:
yarn add react-native-button
How to do it...
1. We'll start by opening the iOS Project in Xcode. The project file has an
.xcodeproj file extension and is located in the ios/ directory in the root of the
project. In our case, the file will be called NativeModuleApp.xcodeproj.
2. We need to make a new file by selecting and right-clicking on the
group/folder that matches the project name, then clicking on New File... as
shown in the following:
3. We'll be making a Cocoa class, so select Cocoa Class and click Next.
4. We'll use HelloManager for the Class name and set the Subclass of
to NSObject, and the Language as Objective-C as shown in the following:
5. After clicking Next, we'll be prompted to choose the directory for the new
class. We want to save it to the NativeModuleApp directory.
6. Creating this new Cocoa class has added two new files to the project: a
header file (HelloManager.h) and an implementation file (HelloManager.m).
7. Inside the header file (HelloManager.h), you should see some generated code
implementing the new HelloManager protocol. We need to import the
React RCTBridgeModule library as well. The file should ultimately look like this:
#import <Foundation/Foundation.h>
#import <React/RCTBridgeModule.h>
@interface HelloManager : NSObject <RCTBridgeModule>
@end
8. The implementation file (HelloManager.m) houses the functionality of our
module. In order for our React Native app to be able to access this module
from the JavaScript layer, we need to register it with the React Bridge. This
is done by adding RCT_EXPORT_MODULE() after the @implementation tag. Also note
that the header file should already be imported into this file as well:
#import "HelloManager.h"
@implementation HelloManager
RCT_EXPORT_MODULE();
@end
9. We need to add the function we'll be exporting to the React Native app.
We'll create a greetUser method that will take two arguments, name and isAdmin.
These arguments will be used to create a greeting message using string
concatenation and then send it back to the JavaScript layer via callback:
#import "HelloManager.h"
@implementation HelloManager
RCT_EXPORT_MODULE();
RCT_EXPORT_METHOD(
greetUser: (NSString *)name isAdmin:(BOOL *)isAdmin callback: (RCTResponseSenderBlock) callback
) {
NSString *greeting =
[NSString stringWithFormat:
@"Welcome %@, you %@ an administrator.", name, isAdmin ? @"are" : @"are not"];
callback(@[greeting]);
}
@end
10. We're ready to switch over to the JavaScript layer, which will have a UI that
will invoke the native HelloManager greetUser method we've just created, then
display its output. Fortunately, the React Native bridge does all of the heavy
lifting for us and leaves us with a simple-to-use JavaScript object that
mimics the NativeModules API. In this example, we'll be using TextInput and
Switch to provide name and the isAdmin value for the native modules method.
Let's start with out imports in App.js:
import React, { Component } from 'react';
import {
StyleSheet,
Text,
View,
NativeModules,
TextInput,
Switch
} from 'react-native';
import Button from 'react-native-button';
11. We can use the NativeModules component we imported to get the HelloManager
protocol we created from the native layer:
const HelloManager = NativeModules.HelloManager;
12. Let's create the App component and define the initial state object. We'll add
a greetingMessage property for saving the message received from the native
module, userName for storing the entered user name, and an isAdmin Boolean
for representing whether the user is an administrator:
export default class App extends Component {
state = {
greetingMessage: null,
userName: null,
isAdmin: false
}
// Defined on following steps
}
13. We're ready to start building the render method. First, we'll need a TextInput
component for getting a user name from the user, and a Switch component
for toggling the isAdmin state:
render() {
return (
<View style={styles.container}>
<Text style={styles.label}>
Enter User Name
</Text>
<TextInput
ref="userName"
autoCorrect={false}
style={styles.inputField}
placeholder="User Name"
onChangeText={(text) => this.setState({ userName: text }) }
/>
<Text style={styles.label}>
Admin
</Text>
<Switch style={styles.radio}
value={this.state.isAdmin}
onValueChange={(value) =>
this.setState({ isAdmin: value })
}
/>
// Continued below
</View>
);
}
14. The UI will also need Button for submitting the callback to the native module
and a Text component for displaying the message returned from the native
module:
render() {
return (
// Defined above.
<Button
disabled={!this.state.userName}
style={[
styles.buttonStyle,
!this.state.userName ? styles.disabled : null
]}
onPress={this.greetUser}
>
Greet (callback)
</Button>
<Text style={styles.label}>
Response:
</Text>
<Text style={styles.message}>
{this.state.greetingMessage}
</Text>
</View>
);
}
15. With the UI rendering the necessary components, we're ready to wire up
the onPress handler of Button to a call to the native layer. This function passes
the displayResults class method as the third parameter as the callback to be
used by the native greetUser function. We'll define displayResults in the next
step:
greetUser = () => {
HelloManager.greetUser(
this.state.userName,
this.state.isAdmin,
this.displayResults
);
}
16. displayResults will need to do two things: blur the TextInput using the refs
associated with the component and set greetingMessage on state to the results
returned from the native module:
displayResults = (results) => {
this.refs.userName.blur();
this.setState({ greetingMessage: results });
}
17. The last step is adding the styles to the layout and styling the app:
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#F5FCFF',
},
inputField:{
padding: 20,
fontSize: 30
},
label: {
fontSize: 18,
marginTop: 18,
textAlign: 'center',
},
radio: {
marginBottom: 20
},
buttonStyle: {
padding: 20,
backgroundColor: '#1DA1F2',
color: '#fff',
fontSize: 18
},
message: {
fontSize: 22,
marginLeft: 50,
marginRight: 50,
},
disabled: {
backgroundColor: '#3C3C3C'
}
});
18. We now have a working React Native app that's able to communicate
directly with the native iOS layer:
How it works...
The app we built in this recipe will serve as the foundation for many of the
following recipes in this chapter. It's also the method Facebook uses to
implement many bundled React Native APIs.
There are several important concepts to keep in mind going forward. Any native
module class we want to use in the JavaScript layer has to extend RCTBridgeModule,
as it contains functionality for registering our class onto the React Native bridge.
We register our class with the RCT_EXPORT_MODULE method call, which registers
methods on the module once the module has been registered. Registering the
module along with its respective methods and properties is what allows us to
interface with the native layer from the JavaScript layer.
The greetUser method is executed when the button is pressed. This function in
turn makes a call to HelloManager.greetUser, passing the userName and isAdmin
properties from state and the displayResults function as a callback. The
displayResults sets the new greetingMessage on state, causing the UI to be refreshed
and the message to be displayed.
There's more...
React Native supports three ways for the native module to communicate back to
the JavaScript layer: callbacks (covered in this recipe), promises, and events. To
see working examples of each one of these techniques, take a look at the source
code bundled with this book. If you're interested in writing native modules using
the Swift language, there's a working example of the same functionality bundled
in the sample application.
See also
An explanation of how React Native applications boot up: https://levelup.git
connected.com/wait-what-happens-when-my-react-native-application-starts-an-in-depth-
look-inside-react-native-5f306ef3250f
A deep dive into how React Native events actually work: https://levelup.gitc
onnected.com/react-native-events-in-gory-details-what-happens-on-the-way-to-listener
s-2cee6c55940c
Rendering custom iOS view
components
While it's very important to leverage the devices, processing power in executing
code on the native layer in our React Native application, it's equally important to
leverage its rendering power to show native UI components. React Native can
render any UI component that's an implementation of UIView inside an
application. These components can be lists, form fields, tables, graphics, and so
on.
For this recipe, we created a React Native application titled NativeUIComponent.
In this recipe, we'll take a native UIButton and expose it as a React Native view
component. You'll be able to set the button label and attach a handler for when
it's tapped.
How to do it...
1. Let's start by opening the iOS project in Xcode. The project file is located
in the ios/ directory of the project and should be
called NativeUIComponent.xcodeproj.
2. Select and right-click on the group that matches your project name and
click on New File...:
3. We'll be making a Cocoa class, so select Cocoa Class and click Next.
4. We'll be creating a button, so let's name the Class Button and set the Subclass
of to UIView and the Language as Objective-C:
5. After clicking Next, we'll be prompted to choose the directory for the new
class. We want to save it to the NativeUIComponent directory to create the class.
6. We're also going to need a ButtonViewManager class as well. You can repeat
steps 2 to 5 with ButtonViewManager as the class name and RCTViewManager as the
subclass.
7. First, we're going to implement our Button UI class. In the header (Button.h)
file, we'll import RCTComponent.h from React and add an onTap property to wire
up our tap event:
#import <UIKit/UIKit.h>
#import "React/RCTComponent.h"
@interface Button : UIView
@property (nonatomic, copy) RCTBubblingEventBlock onTap;
@end
8. Let's work on the implementation file (Button.m). We'll start by creating
references for our UIButton instance and the string that will hold the button
label:
#import "Button.h"
#import "React/UIView+React.h"
@implementation Button {
UIButton *_button;
NSString *_buttonText;
}
// Defined in following steps
9. The bridge will look for a setter for the buttonText property. This is where
we'll set the UIButton instance title field:
-(void) setButtonText:(NSString *)buttonText {
NSLog(@"Set text %@", buttonText);
_buttonText = buttonText;
if(_button) {
[_button setTitle:
buttonText forState:UIControlStateNormal];
[_button sizeToFit];
}
}
10. Our Button will accept an onTap event handler from the React Native app. We
need to wire this to our UIButton instance through an action selector:
- (IBAction)onButtonTap:(id)sender {
self.onTap(@{});
}
11. We need to instantiate the UIButton and place it inside a React Subview. We'll
call this method layoutSubviews:
-(void) layoutSubviews {
[super layoutSubviews];
if( _button == nil) {
_button =
[UIButton buttonWithType:UIButtonTypeRoundedRect];
[_button addTarget:self action:@selector(onButtonTap:)
forControlEvents:UIControlEventTouchUpInside];
[_button setTitle:
_buttonText forState:UIControlStateNormal];
[_button sizeToFit];
[self insertSubview:_button atIndex:0];
}
}
12. Let's import the React RCTViewManager in the ButtonViewManager.h header file:
#import "React/RCTViewManager.h"
@interface ButtonViewManager : RCTViewManager
@end
13. Now we need to implement our ButtonViewManager, which will interface with
our React Native application. Let's work on the implementation file
(ButtonViewManager.m) to make this happen. We use RCT_EXPORT_VIEW_PROPERTY to
pass along the buttonText property and onTap method to the React Native
layer:
#import "ButtonViewManager.h"
#import "Button.h"
#import "React/UIView+React.h"
@implementation ButtonViewManager
RCT_EXPORT_MODULE()
- (UIView *)view {
Button *button = [[Button alloc] init];
return button;
}
RCT_EXPORT_VIEW_PROPERTY(buttonText, NSString);
RCT_EXPORT_VIEW_PROPERTY(onTap, RCTBubblingEventBlock);
@end
14. We are ready to switch over to the React Native layer. We're going to need a
custom Button component, so let's create a new components folder in the root of
the project with a new Button.js file inside of it. We'll also need to import
the requireNativeComponent component from React Native for interfacing with
our native UI component:
import React, { Component } from 'react';
import {
StyleSheet,
Text,
View
} from 'react-native';
import Button from './components/Button';
15. The Button component will grab the native Button module we created earlier
via the requireNativeComponent React Native helper. The call takes a string to be
used as the component's name in the React Native layer as the first
parameter, and the second takes the Button component in the file, effectively
wiring the two together:
export default class Button extends Component {
render() {
return <ButtonView {...this.properties} />;
}
}
const ButtonView = requireNativeComponent('ButtonView', Button);
16. We're ready to build out the main App component in the App.js file in the root
of the project. We'll start with the imports, which will include the Button
component we created in the last two steps:
import React, { Component } from 'react';
import {
StyleSheet,
Text,
View
} from 'react-native';
import Button from './components/Button';
17. Let's define the App component and the initial state object. The count property
will keep track of the number of times the Button component has been
pressed:
export default class App extends Component {
state = {
count: 0
}
// Defined on following steps
}
18. We're ready to define the render method, which will just consist of the Button
component, along with a Text element for displaying the current button press
count:
render() {
return (
<View style={styles.container}>
<Button buttonText="Click Me!"
onTap={this.handleButtonTap}
style={styles.button}
/>
<Text>Button Pressed Count: {this.state.count}</Text>
</View>
);
}
19. You may recall that the Button component we created has an onTap property,
which takes a callback function. In this case we'll just use this function to
increase the counter that lives on state:
handleButtonTap = () => {
this.setState({
count: this.state.count + 1
});
}
20. Let's wrap up this recipe with a few basic styles:
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#F5FCFF',
},
button: {
height: 40,
width: 80
}
});
21. The app is complete! When the button is pressed, the function passed
to onTap will be executed, increasing the counter by one:
How it works...
In this recipe, we exposed a basic native UI component. This is the same method
by which all of the UI components built into React Native (for example, Slider,
Picker, and ListView) were created.
The most important requirement in creating UI components is that your ViewManager extends
RCTViewManager and returns an instance of UIView. In our case, we're wrapping UIButton with a
React-specific UIView extension, which improves our ability to layout and style the component.
The next important factor is sending properties and reacting to component
events. In step 13, we used the RCT_EXPORT_VIEW_PROPERTY method provided by React
Native to register the buttonText and onTap view properties that will come from the
JavaScript layer to the Button component. That Button component is then created
and returned to be used in the JavaScript layer:
- (UIView *)view {
Button *button = [[Button alloc] init];
return button;
}
Exposing custom Android modules
Often, you'll find the need for React Native applications to interface with native
iOS and Android code. Having discussed integrating native iOS modules, now
it's time to cover the equivalent recipes in Android.
This recipe will take us through writing our first Android native module. We're
going to create a HelloManager native module with a greetUser method that takes name
and an isAdmin Boolean as arguments, which will return a greeting message that
we'll display in the UI.
Getting ready
For this recipe, we'll need to create another pure React Native app. Let's name
this project NativeModuleApp as well.
We'll also be making use of the react-native-button library again, which can be
installed with npm:
npm install react-native-button --save
Alternatively, it can be installed using yarn:
yarn add react-native-button
How to do it...
1. We'll start by opening the new project's Android code in Android Studio.
From the Android Studio welcome screen, you can select Open an existing
Android Studio project, then select the android directory inside of the project
folder.
2. Once the project has loaded, let's open the project explorer (that is, the
directory tree) on the left side of Android Studio and expand the package
structure to find the Java source files, which should live in
app/java/com.nativemoduleapp. The folder should already have two .java files in
it, MainActivity and MainApplication:
3. Right-click on the com.nativemoduleapp package, select New | Java Class,
and name the class HelloManager. Also, be sure to set the Kind field to Class:
4. We'll also need a HelloPackage class in the same directory. You can repeat
steps 2 and 3 to create this class, simply applying the new name and
keeping the Kind field set to Class.
5. Let's start by implementing our HelloManager native module. We'll start with
the package name and the dependencies we'll need in this file:
package com.nativemoduleapp;
import com.facebook.react.bridge.Callback;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
6. ReactContextBaseJavaModule is the base class for all React Native modules, so
we'll be creating the HelloManager class as a subclass of it. We also need to
define a getName method, which is used for registering native modules with
the React Native bridge. This is one difference from the iOS native module
implementations, as those are defined via class name:
public class HelloManager extends ReactContextBaseJavaModule {
public HelloManager(ReactApplicationContext reactContext) {
super(reactContext);
}
@Override
public String getName() {
return "HelloManager";
}
}
7. Now that we've set up our HelloManager native module, it's time to add the
greetUser method to it, which will expect as arguments, name, isAdmin, and the
callback that will be executed to send the message to the React Native
layer:
public class HelloManager extends ReactContextBaseJavaModule {
// Defined in previous steps
@ReactMethod
public void greetUser(String name, Boolean isAdmin, Callback callback) {
System.out.println("User Name: " + name + ", Administrator: " + (isAdmin ? "Yes" : "No"));
String greeting = "Welcome " + name + ", you " + (isAdmin ? "are" : "are not") + " an administrator";
callback.invoke(greeting);
}
}
8. Another step that's unique to Android is having to register the native
module with the application, which is a two-step process. The first step is to
add our HelloManager module to the HelloPackage class we created earlier. We'll
start with the dependencies for HelloPackage.java:
package com.nativemoduleapp;
import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
9. The implementation of HelloPackage simply follows the pattern provided by
the official documentation (https://facebook.github.io/react-native/docs/native-m
odules-android.html). The most important piece here is the call to modules.add,
where a new instance of HelloManager is passed in with reactContext as its
parameter:
public class HelloPackage implements ReactPackage {
@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
return Collections.emptyList();
}
@Override
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
List<NativeModule> modules = new ArrayList<>();
modules.add(new HelloManager(reactContext));
return modules;
}
}
10. The second step in registering the native module with the React Native app
is to add HelloPackage to the MainApplication module. Most of the code here is
generated by the React Native bootstrapping process. The getPackages
method needs to be updated to take both new MainReactPackage() and new
HelloPackage() as arguments passed to Arrays.asList:
package com.nativemoduleapp;
import android.app.Application;
import com.facebook.react.ReactApplication;
import com.facebook.react.ReactNativeHost;
import com.facebook.react.ReactPackage;
import com.facebook.react.shell.MainReactPackage;
import com.facebook.soloader.SoLoader;
import java.util.Arrays;
import java.util.List;
public class MainApplication extends Application implements ReactApplication {
private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) {
@Override
public boolean getUseDeveloperSupport() {
return BuildConfig.DEBUG;
}
@Override
protected List<ReactPackage> getPackages() {
return Arrays.asList(
new MainReactPackage(),
new HelloPackage()
);
}
@Override
protected String getJSMainModuleName() {
return "index";
}
};
@Override
public ReactNativeHost getReactNativeHost() {
return mReactNativeHost;
}
@Override
public void onCreate() {
super.onCreate();
SoLoader.init(this, /* native exopackage */ false);
}
}
11. We're all done on the Java portion of this recipe. We need to build our UI,
which will invoke the native HelloManager greetUser method and display its
output. In this example, we'll be using TextInput and Switch to provide name
and the isAdmin value for the native module method. This is the same
functionality as we implemented on iOS in the Exposing custom iOS
modules recipe. Let's get to building out App.js, starting with the
dependencies we'll need:
import React, { Component } from 'react';
import {
StyleSheet,
Text,
View,
NativeModules,
TextInput,
Switch,
DeviceEventEmitter
} from 'react-native';
import Button from 'react-native-button';
12. We need to make a reference to the HelloManager object that lives on the
imported NativeModules component:
const { HelloManager } = NativeModules;
13. Let's create the App class and the initial state:
export default class App extends Component {
state = {
userName: null,
greetingMessage: null,
isAdmin: false
}
}
14. We're ready to define the component's render function. This piece of code
will not be described in great detail, as it's basically the same render function
defined in the Exposing custom iOS modules recipe at the beginning of this
chapter:
render() {
return (
<View style={styles.container}>
<Text style={styles.label}>
Enter User Name
</Text>
<TextInput
ref="userName"
autoCorrect={false}
style={styles.inputField}
placeholder="User Name"
onChangeText={(text) => this.setState({ userName: text })
}
/>
<Text style={styles.label}>
Admin
</Text>
<Switch
style={styles.radio}
onValueChange={
value => this.setState({ isAdmin: value })
}
value={this.state.isAdmin}
/>
<Button
disabled={!this.state.userName}
style={[
styles.buttonStyle,
!this.state.userName ? styles.disabled : null
]}
onPress={this.greetUser}
>
Greet
</Button>
<Text style={styles.label}>
Response:
</Text>
<Text style={styles.message}>
{this.state.greetingMessage}
</Text>
</View>
);
}
15. With the UI rendering the necessary components, we now need to wire up
the onPress handler of Button to make the native call via HelloManager.greetUser:
updateGreetingMessage = (result) => {
this.setState({
greetingMessage: result
});
}
greetUser = () => {
this.refs.userName.blur();
HelloManager.greetUser(
this.state.userName,
this.state.isAdmin,
this.updateGreetingMessage
);
}
16. We'll add styles to layout and style the app. Again, these are the same styles
as used in the Exposing custom iOS modules recipe at the beginning of this
chapter:
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#F5FCFF',
},
inputField:{
padding: 20,
fontSize: 30,
width: 200
},
label: {
fontSize: 18,
marginTop: 18,
textAlign: 'center',
},
radio: {
marginBottom: 20
},
buttonStyle: {
padding:20,
backgroundColor:'#1DA1F2',
color:'#fff',
fontSize:18
},
message:{
fontSize:22,
marginLeft:50,
marginRight:50,
},
disabled:{
backgroundColor:'#3C3C3C'
}
});
17. Thefinalappshouldlooksimilartothefollowingscreenshot:
Howitworks...
Thisrecipecoversthefoundationformuchofwhatwe'llbedoingwithadding
nativeAndroidmodulesinfuturerecipes.Allnativemoduleclassesneedto
extendReactContextBaseJavaModule,implementtheconstructor,anddefinethegetName
method.AllmethodsthatshouldbeexposedtotheReactNativelayerneedto
havethe@ReactMethodannotation.CreatingaReactNativeAndroidnativemodule
hasmoreoverheadascomparedwithiOS,sinceyouhavetoalsowrapyour
moduleinaclassthatimplementsReactPackage(inthisrecipe,that'stheHelloPackage
module),andregisterthepackagewiththeReactNativeproject.Thisisdonein
steps7and8.
IntheJavaScriptportionoftherecipe,thegreetUserfunctionisexecutedwhenthe
userpressestheButtoncomponent.This,inturn,makesacallto
HelloManager.greetUser,passingalongtheuserNameandisAdminpropertiesfromstate
andtheupdateGreetingMessagemethodasacallback.TheupdateGreetingMessagesetsthe
newgreetingMessageonstate,causingarefreshoftheUIandthemessagetobe
displayed.
RenderingcustomAndroidview
components
OnereasonReactNativehasgainedsomuchpopularitysofarisitsabilityto
rendertrulynativeUIcomponents.WithnativeUIcomponentsonAndroid,
we'reabletoleveragenotonlytheGPUrenderingpower,butwealsogetthe
nativelookandfeelofnativecomponents,includingnativefonts,colors,and
animations.WebandhybridapplicationsonAndroiduseCSSpolyfillsto
simulateanativeanimationbut,inReactNative,wecangettherealthing.
We'llneedanewpureReactNativeappforthisrecipe.Let'sname
itNativeUIComponent.Inthisrecipe,we'lltakeanativeButtonandexposeitasa
ReactNativeviewcomponent.
Howtodoit...
1. Let'sstartbyopeningtheAndroidprojectinAndroidStudio.Inthe
AndroidStudiowelcomescreen,selectOpenanexistingAndroidStudio
projectandopentheandroiddirectoryoftheproject.
2. Opentheprojectexplorerandexpandthepackagestructureuntilyoucan
seetheJavasourcefiles(forexample,app/java/com.nativeuicomponent):
3. Right-clickonthepackageandselectNew|JavaClass.UseButtonViewManager
fortheclassnameandsettheKindfieldtoClass.
4. UsethesamemethodtoalsocreateaButtonPackageclass.
5. Let'sbeginimplementingourButtonViewManagerclass,whichmustbea
subclassofSimpleViewManager<View>.We'llstartwiththeimportsanddefinethe
classitself:
packagecom.nativeuicomponent;
importandroid.view.View;
importandroid.widget.Button;
importcom.facebook.react.bridge.Arguments;
importcom.facebook.react.bridge.ReactContext;
importcom.facebook.react.bridge.WritableMap;
importcom.facebook.react.uimanager.SimpleViewManager;
importcom.facebook.react.uimanager.ThemedReactContext;
importcom.facebook.react.uimanager.annotations.ReactProp;
importcom.facebook.react.uimanager.events.RCTEventEmitter;
publicclassButtonViewManagerextendsSimpleViewManager<Button>implementsView.OnClickListener{
//Definedonfollowingsteps
}
ThefileclassnameButtonViewManagerfollowstheAndroidnamingconventionofaddingthe
suffixViewManagertoanyViewcomponent.
6. Let'sstarttheclassdefinitionwiththegetNamemethodthatreturnsthestring
namewe'reassigningthecomponent,whichinthiscaseisButtonView:
publicclassButtonViewManagerextendsSimpleViewManager<Button>implementsView.OnClickListener{
@Override
publicStringgetName(){
return"ButtonView";
}
//Definedonfollowingsteps.
}
7. ThecreateViewInstancemethodisrequiredfordefininghowReactshould
initializethemodule:
@Override
protectedButtoncreateViewInstance(ThemedReactContextreactContext){
Buttonbutton=newButton(reactContext);
button.setOnClickListener(this);
returnbutton;
}
8. setButtonTextwillbeusedfromthepropertiesontheReactNativeelementto
setthetextonthebutton:
@ReactProp(name="buttonText")
publicvoidsetButtonText(Buttonbutton,StringbuttonText){
button.setText(buttonText);
}
9. TheonClickmethoddefineswhatwillhappenwhenthebuttonispressed.
ThismethodusesRCTEventEmittertohandlereceivingeventsfromtheReact
Nativelayer:
@Override
publicvoidonClick(Viewv){
WritableMapmap=Arguments.createMap();
ReactContextreactContext=(ReactContext)v.getContext();
reactContext.getJSModule(RCTEventEmitter.class).receiveEvent(v.getId(),"topChange",map);
}
10. Justlikeinthelastrecipe,weneedtoaddButtonViewManagertoButtonPackage;
however,thistime,we'redefiningitasViewManagerandnotNativeModule:
packagecom.nativeuicomponent;
importcom.facebook.react.ReactPackage;
importcom.facebook.react.bridge.NativeModule;
importcom.facebook.react.bridge.ReactApplicationContext;
importcom.facebook.react.uimanager.ViewManager;
importjava.util.Arrays;
importjava.util.Collections;
importjava.util.List;
publicclassButtonPackageimplementsReactPackage{
@Override
publicList<ViewManager>createViewManagers(ReactApplicationContextreactContext){
returnArrays.<ViewManager>asList(newButtonViewManager());
}
@Override
publicList<NativeModule>createNativeModules(ReactApplicationContextreactContext){
returnCollections.emptyList();
}
}
11. ThelaststepintheJavalayerisaddingButtonPackage
toMainApplication.MainApplication.javaalreadyhasquiteabitofboilerplate
codeinit,andwe'llonlyneedtochangethegetPackagesmethod:
@Override
protectedList<ReactPackage>getPackages(){
returnArrays.<ReactPackage>asList(
newMainReactPackage(),
newButtonPackage()
);
}
12. SwitchingovertotheJavaScriptlayer,let'sbuildoutourReactNativeapp.
First,let'screateanewButtoncomponentincomponents/Button.jsinthe
project'srootdirectory.Thisiswherethenativebuttonwillliveinsidethe
ReactNativelayeroftheapp.Therendermethodusesthenativebutton
asButtonView,whichwe'lldefineinthenextstep:
importReact,{Component}from'react';
import{requireNativeComponent,View}from'react-native';
exportdefaultclassButtonextendsComponent{
onChange=(event)=>{
if(this.properties.onTap){
this.properties.onTap(event.nativeEvent.message);
}
}
render(){
return(
<ButtonView
{...this.properties}
onChange={this.onChange}
/>
);
}
}
13. WecancreatethenativebuttonasaReactNativecomponentwith
therequireNativeComponenthelper,whichtakesthreeparameters:thestring
ButtonViewtodefinethecomponentsname,theButtoncomponentdefinedin
thepreviousstep,andtheoptionsobject.There'smoreinformationonthis
objectintheHowitworks...sectionattheendofthisrecipe:
constButtonView=requireNativeComponent(
'ButtonView',
Button,{
nativeOnly:{
onChange:true
}
}
);
14. We'rereadytodefinetheAppclass.Let'sstartwithdependencies,including
theButtoncomponentcreatedpreviously:
importReact,{Component}from'react';
import{
StyleSheet,
Text,
View
}from'react-native';
importButtonfrom'./components/Button';
15. TheAppcomponentinthisrecipeisessentiallythesameastheRendering
customiOSviewcomponentsrecipeearlierinthischapter.ThecustomonTap
propertyisfiredwhentheButtoncomponentispressed,adding1tothecount
propertyonstate:
exportdefaultclassAppextendsComponent{
state={
count:0
}
onButtonTap=()=>{
this.setState({
count:this.state.count+1
});
}
render(){
return(
<Viewstyle={styles.container}>
<ButtonbuttonText="PressMe!"
onTap={this.onButtonTap}
style={styles.button}
/>
<Text>
ButtonPressedCount:{this.state.count}
</Text>
</View>
);
}
}
16. Let'saddafewstylestolayoutandsizetheapp'sUI:
conststyles=StyleSheet.create({
container:{
flex:1,
justifyContent:'center',
alignItems:'center',
backgroundColor:'#F5FCFF',
},
button:{
height:40,
width:150
}
});
17. Thefinalappshouldlooksimilartothefollowingscreenshot:
Howitworks...
Whendefininganativeview,aswedidwiththeButtonViewManagerclass,itmust
extendSimpleViewManagerandrenderatypethatextendsView.Inourrecipe,we
renderedaButtonview,andweusethe@ReactPropannotationfordefining
properties.WhenweneedtocommunicatebacktotheJavaScriptlayer,wefire
aneventfromthenativecomponent,whichweimplementedinstep9ofthis
recipe.
Instep12,wecreatedanonChangelistener,whichwillexecutetheeventhandler
passedinfromtheAndroidlayer(event.nativeEvent.message).
RegardingtheuseofthenativeOnlyoptiononstep13,fromtheReactNative
documents:
Sometimesyou'llhavesomespecialpropertiesthatyouneedtoexposeforthenative
component,butdon'tactuallywantthemaspartoftheAPIfortheassociatedReact
component.Forexample,SwitchhasacustomonChangehandlerfortherawnativeevent,and
exposesanonValueChangehandlerpropertythatisinvokedwithjusttheBooleanvalue,rather
thantherawevent.Sinceyoudon'twantthesenativeonlypropertiestobepartoftheAPI,
youdon'twanttoputtheminpropTypes,butifyoudon't,you'llgetanerror.Thesolutionis
simplytocallthemoutviathenativeOnlyoption.
HandlingtheAndroidbackbutton
AnimportantfeaturethatallAndroiddeviceshaveisadedicatedbackbutton.
Bydefault,thebehaviorofthisbackbuttonistogototheprevious
runningactivity.SinceReactNativegenerallyonlyhasonemainReactActivity,
thiswilltakeyououtofyourapplication.Sometimes,wemaynotwanttohave
thisdefaultbehaviorandusethebackbuttoninsideourReactNative
application.
ThisrecipewillcreateasimpleexampleusingReactNative'slatestnavigation
implementation,NavigationExperimental,andattachabackbuttonlistenertostep
backthroughthenavigationstack.
Gettingready
Forthisrecipe,itmaybeagoodideatofamiliarizeyourselfwiththe
NavigationExperimentaldocumentation.We'vedemonstratedthisintheAdding
navigationtoyourapplicationrecipeinChapter1,SettingUpYourEnvironment.
Forthisrecipe,wecreatedaReactNativeapplicationtitledBackButton.
Howtodoit...
1. InordertomakeameaningfulexampleofusingtheAndroidbackbutton,
we'regoingtohavetoimplementNavigationExperimentalforourapplication.
Let'sstartoffbymakingaButtoncomponentthatwillbeusedtomanually
betweennavigationstates.Createafile,Button.js,inyourproject'sroot
directory(whereindex.android.jsislocated)andaddthefollowingcode:
importReact,{Component}from'react';
import{
StyleSheet,
Text,
View,
PixelRatio,
TouchableOpacity
}from'react-native';
exportdefaultclassButtonextendsComponent{
render(){
return(
<TouchableOpacity
style={styles.row}
onPress={this.properties.onPress}>
<Textstyle={styles.buttonText}>
{this.properties.text}
</Text>
</TouchableOpacity>
)
}
}
conststyles=StyleSheet.create({
row:{
padding:15,
backgroundColor:'white',
borderBottomWidth:1/PixelRatio.get(),
borderBottomColor:'#CDCDCD',
},
buttonText:{
fontSize:17,
fontWeight:'500',
}
});
2. Nowwe'regoingtoneedascenethatwillberenderedbytheNavigator.
ThisscenewillholdtheButtoncomponentwecreatedinthepreviousstep.
Createafile,InitialScene.js,andaddthefollowing:
importReact,{Component}from'react';
import{
StyleSheet,
Text,
View,
PixelRatio,
ScrollView
}from'react-native';
importButtonfrom'./Button';
exportdefaultclassInitialSceneextendsComponent{
render(){
return(
<ScrollViewstyle={styles.scrollView}>
<Textstyle={styles.row}>
Route:{this.properties.route.key}
</Text>
<Button
text="Next"
onPress={this.properties.onPushRoute}
/>
<Button
text="Back"
onPress={this.properties.onPopRoute}
/>
</ScrollView>
);
}
}
Youcanaddthefollowingstylesattheendofthefiletogivethe
scenesomestyling:
conststyles=StyleSheet.create({
navigator:{
flex:1,
},
scrollView:{
marginTop:64
},
row:{
padding:15,
backgroundColor:'white',
borderBottomWidth:1/PixelRatio.get(),
borderBottomColor:'#CDCDCD',
},
rowText:{
fontSize:17,
},
buttonText:{
fontSize:17,
fontWeight:'500',
},
});
3. ThenextstepistoimplementtheactualNavigatoritself.Thiswillcontain
theNavigationCardStack,instructionsonhowtorenderasceneand,most
importantlyinthiscase,ourbackbuttoneventhandler.Let'sgetrightinto
it;createaNavigator.jsfileandaddthefollowing:
importReact,{Component}from'react';
import{
StyleSheet,
Text,
View,
NavigationExperimental
}from'react-native';
importInitialScenefrom'./InitialScene';
const{
CardStack:NavigationCardStack
}=NavigationExperimental;
exportdefaultclassNavigatorextendsComponent{
constructor(properties,context){
super(properties,context);
this._onPushRoute=
this.properties.onNavigationChange.bind(null,'push');
this._onPopRoute=
this.properties.onNavigationChange.bind(null,'pop');
}
_renderScene=(sceneProps)=>{
return(
<InitialScene
route={sceneProps.scene.route}
onPushRoute={this._onPushRoute}
onPopRoute={this._onPopRoute}
/>
);
}
render(){
return(
<NavigationCardStack
onNavigationBack={this._onPopRoute}
navigationState={this.properties.navigationState}
renderScene={this._renderScene}
style={{flex:1}}
/>
);
}
}
Herewe'rerenderingtheInitialScenecomponentthatwecreatedand
puttingitonNavigationCardStack.
4. Nowit'stimetoaddsupportforthebackbutton.We'llbemodifyingour
Navigatorsinceitholdsthepropertythathandlesnavigationstatechange.
First,weneedtoimportBackAndroidfrom'react-native',soaddthefieldtothe
importblockafterNavigationExperimental.Attheendoftheconstructor,we're
goingtoaddtheeventlistenerforthebackbutton:
BackAndroid.addEventListener
('hardwareBackPress',this.onAndroidBackPress);
Lastly,weneedtoimplementthecallbackthatwepassedintotheevent
listener:
onAndroidBackPress=()=>{
if(this.properties.navigationState.index){
this._onPopRoute();
returntrue;
}
returnfalse;
}
5. Totiethisalltogether,weneedtorendertheNavigatorfromour
application'sentryclass.We'llstartbyimportingthenecessary
dependencies,openindex.android.js.
WeneedtoimportNavigationExperimentalfrom'react-native',ourNavigator
component,andgetareferencetoStateUtilsfromNavigationExperimental:
import{
AppRegistry,
StyleSheet,
Text,
View,
NavigationExperimental
}from'react-native';
const{
StateUtils:NavigationStateUtils
}=NavigationExperimental;
importNavigatorfrom'./Navigator';
6. Then,torendertheNavigator,overwritetheimplementationofthe
applicationclasswiththefollowing:
componentWillMount(){
this.state={
navigationState:{
index:0,
routes:[
{key:'Initial'}
]
}
};
}
_onNavigationChange=(type)=>{
letnavigationState=this.state.navigationState,
newNavigationState;
switch(type){
case'push':
newNavigationState=
NavigationStateUtils.push(navigationState,{
key:'Route-'+Date.now()
});
break;
case'pop':
newNavigationState=
NavigationStateUtils.pop(navigationState);
break;
}
if(newNavigationState!==navigationState){
this.setState({navigationState:newNavigationState});
}
}
render(){
return(
<Navigator
navigationState={this.state.navigationState}
onNavigationChange={this._onNavigationChange}
/>
);
}
Yourapplicationshouldlookasshowninthefollowingscreenshot,
andyoushouldbeabletousethebackbuttontonavigateupthe
stack:
Howitworks...
TheAndroidbackbuttonhandlerisquitetrivialtoimplement.Itshould,
however,beusefulinameaningfulwaythatfeelsnaturaltotheapplication.The
implementationoccursinstep4.Topreventthedefaultaction—inthiscase,it
wouldexitoutoftheapplication—wehavetoreturntruefromoureventlistener.
AddingNativeFunctionality-PartII
Inthischapter,wewillcoverthefollowingrecipes:
Reactingtochangesinapplicationstate
Copyingandpastingcontent
Receivingpushnotifications
AuthenticatingviatouchIDorfingerprintsensor
Hidingapplicationcontentwhenmultitasking
BackgroundprocessingoniOS
BackgroundprocessingonAndroid
PlayingaudiofilesoniOS
PlayingaudiofilesonAndroid
Introduction
Inthischapter,wewillcontinuewithmorerecipesthattouchondifferent
aspectsofwritingReactNativeappsthatinteractwithnativeiOSandAndroid
code.Wewillcoverexampleappsthatleveragebuilt-inandcommunitycreated
modules.Therecipescoverarangeoftopics,fromrenderingabasicbuttonto
creatingamultithreadedprocessthatdoesnotblockthemainapplication
threads.
Reactingtochangesinapplication
state
Theaveragemobiledeviceuserhasseveralappsthattheyuseonaregularbasis.
Ideally,alongwiththeothersocialmediaapps,games,mediaplayers,andmore,
userswillalsobeusingyourReactNativeapp.Anyspecificusermayspenda
shorttimeineachapplicationbecauseheorshemultitasks.Whatifwewanted
toreacttowhentheuserleavesourappandre-enters?Wecouldusethisasa
chancetosyncdatawiththeserver,ortotelltheuserthatwe'rehappytosee
themreturn,ortopolitelyaskforaratingontheappstore.
Thisrecipewillcoverthebasicsofreactingtochangesinthestateofthe
application,whichistosayreactingtowhentheappisintheforeground
(active),background,orinactive.
Forthisrecipe,let'screateanewpureReactNativeapptitledAppStateApp.
Howtodoit...
1. Fortunately,ReactNativeprovidessupportforlisteningtochangestothe
stateoftheappthroughtheAppStatemodule.Let'sbeginbuildingouttheapp
byaddingdependenciestotheApp.jsfile,asfollows:
importReact,{Component}from'react';
import{
AppState,
StyleSheet,
Text,
View
}from'react-native';
2. Intherecipe,we'regoingtokeeptrackofthepreviousstatetoseewhere
theusercamefrom.Ifit'stheirfirsttimeenteringtheapp,wewillwelcome
them,andifthey'rereturning,wewillwelcomethembackinstead.Todo
so,weneedtokeepareferencetothepreviousandcurrentappstates.We'll
useinstancevariablespreviousAppStateandcurrentAppStatesinsteadofusing
stateforthispurpose,simplytoavoidpotentialnamingconfusion.We'll
usestatetoholdthestatusmessagetotheuser,asfollows:
exportdefaultclassAppextendsComponent{
previousAppState=null;
currentAppState=null;
state={
appStatusMessage='Welcome!'
}
//Definedonfollowingsteps
}
3. Whenthecomponentmounts,we'llusetheAppStatecomponenttoaddan
eventlistenertothechangeevent.Whenevertheapp'sstatechanges(for
example,whentheappisbackgrounded),thechangeeventwillbefired,
whereuponwe'llfireourhandleAppStateChangehandler,definedinthenext
step,asfollows:
componentWillMount(){
AppState.addEventListener('change',this.handleAppStateChange);
}
4. ThehandleAppStateChangemethodwillreceivetheappStateasaparameter,
whichwecanexpecttobeoneofthreestrings:inactiveiftheappis
unloadedfrommemory,backgroundiftheappisinmemoryanbackgrounded,
andactiveiftheappisforegrounded.We'lluseaswitchstatementtoupdate
thestatusMessageonstateaccordingly:
handleAppStateChange=(appState)=>{
letstatusMessage;
this.previousAppState=this.currentAppState;
this.currentAppState=appState;
switch(appState){
case'inactive':
statusMessage="GoodBye.";
break;
case'background':
statusMessage="AppIsHidden...";
break;
case'active':
statusMessage='WelcomeBack!'
break;
}
this.setState({statusMessage});
}
5. Therendermethodisverybasicinthisrecipe,sinceitonlyneedstodisplay
thestatusmessagetotheuser,asfollows:
render(){
return(
<Viewstyle={styles.container}>
<Textstyle={styles.welcome}>
{this.state.appStatus}
</Text>
</View>
);
}
6. Thestylesforthisapparesimilarlybasic,addingfontsize,color,and
margin,asfollows:
conststyles=StyleSheet.create({
container:{
flex:1,
justifyContent:'center',
alignItems:'center',
backgroundColor:'#fff',
},
welcome:{
fontSize:40,
textAlign:'center',
margin:10,
},
instructions:{
textAlign:'center',
color:'#333333',
marginBottom:5,
},
});
7. Thecompletedappshouldnowdisplaytheappropriatestatusmessage
dependingonthestateoftheapponagivendevice.
Howitworks...
Inthisrecipe,wemadeuseofthebuilt-inAppStatemodule.Themodulelistensto
theActivityeventsonAndroid,andoniOSitusesNSNotificationCentertoregistera
listeneronvariousUIApplicationevents.Notethatbothplatformssupport
theactiveandbackgroundstates;however,theinactivestateisaniOSonlyconcept.
Androiddoesnotexplicitlysupporttheinactivestateduetoitsmultitasking
implementation,soonlytogglesappsbetweenbackgroundandactivestates.To
achievetheequivalentoftheiOSinactivestateonAndroid,seetheHiding
applicationcontentwhenmultitaskingrecipelaterinthischapter.
Copyingandpastingcontent
Oneofthemostusedfeaturesinbothdesktopandmobileoperatingsystemsis
theclipboardforcopyingandpastingcontent.Acommonscenarioonmobileis
fillingformswithlengthytext,suchaslongemailaddressesor
passwords.Insteadoftypingitwithafewtypos,itwouldbeeasiertojustopen
yourcontactsapplicationandcopytheemailfromthereandpasteitinto
yourTextInputfield.
ThisrecipewillshowabasicexampleonbothAndroidandiOSofhowwecan
copyandpastetextinsideourReactNativeapplication.Inoursampleapp,we
willhavebothastaticTextviewandaTextInputfieldthatyoucancopyits
contentstotheclipboard.Also,therewillbeabuttonthatoutputsthecontentsof
theclipboardtotheview.
Gettingready
Forthisrecipe,wecreatedaReactNativeapplicationtitledCopyPasteApp.
Inthisrecipe,wewillbeusingreact-native-buttonagain.Installitwithnpm:
npminstallreact-native-button
Alternatively,wecanuseyarn:
yarnaddreact-native-button
Howtodoit...
1. Let'sstartoffbycreatingaClipboardTextcomponentthatbothuses
aTextcomponenttodisplaytextandprovidestheabilitytocopyitscontents
totheclipboardvialongpress.Let'screateacomponentfolderintherootof
theproject,andaClipboardText.jsfileinsideofit.We'llstartbyimporting
dependencies,asfollows:
importReact,{Component}from'react';
import{
StyleSheet,
Text,
View,
Clipboard,
TextInput
}from'react-native';
importButtonfrom'react-native-button';
2. Nextwe'lldefinetheAppclassandtheinitialstate.Wewilluse
theclipboardContentpropertyonstateforstoringtextbeingpastedfromthe
clipboardintotheUI,asfollows:
exportdefaultclassAppextendsComponent{
state={
clipboardContent:null
}
//Definedinfollowingsteps
}
3. TheUIwillhaveoneTextcomponentwhosetextwillbycopyablevialong
press.Let'sdefinethecopyToClipboardmethod.We'llgrabtheinputvia
itsref(whichwe'lldefinelater),andaccessthecomponent'stextvia
itsprops.childrenproperty.Oncethetexthasbeenstoredinalocalvariable,
wesimplypassittosetStringmethodofClipboardtocopythetexttothe
clipboard,asfollows:
copyToClipboard=()=>{
constsourceText=this.refs.sourceText.props.children;
Clipboard.setString(sourceText);
}
4. Similarly,we'llalsoneedamethodthatwillpastetextintotheapp'sUI
fromtheclipboard.ThismethodwillusetheClipboard'sgetStringmethod,
andsavethereturnedstringtoclipboardContentpropertyofstate,re-
renderingtheapp'sUItoreflectthepastedtext,asfollows:
getClipboardContent=async()=>{
constclipboardContent=awaitClipboard.getString();
this.setState({
clipboardContent
});
}
5. Therendermethodwillbemadeupoftwosections:thefirstismadeof
thingstocopy,andthesecondisawayforpastingtextfromtheclipboard
intotheUI.Let'sstartwiththefirstsection,whichconsistsofaTextinput
whoseonLongPresspropiswiredtothecopyToClipboardmethodwecreatedin
step3,andatextinputfornormalnativecopy/pasting:
render(){
return(
<Viewstyle={styles.container}>
<Textstyle={styles.instructions}>
TapandHoldthenextlinetocopyittotheClipboard:
</Text>
<Text
ref="sourceText"
onLongPress={this.copyToClipboard}
>
ReactNativeCookbook
</Text>
<Textstyle={styles.instructions}>
InputsometextintotheTextInputbelowandCut/Copyas
younormallywould:
</Text>
<TextInputstyle={styles.textInput}/>
//Definedonnextstep
</View>
);
}
6. ThesecondportionoftheUIconsistsofaTextcomponentfordisplayingthe
currentvaluesavedinclipboardContentonstate,andabuttonthatwillpaste
fromtheclipboardusingthegetClipboardContentmethodwedefinedinstep4:
render(){
return(
<Viewstyle={styles.container}>
//Definedinpreviousstep
<Viewstyle={styles.row}>
<Textstyle={styles.rowText}>
ClipboardContents:
</Text>
</View>
<Viewstyle={styles.row}>
<Textstyle={styles.content}>
{this.state.clipboardContent}
</Text>
</View>
<Button
containerStyle={styles.buttonContainer}
style={styles.buttonStyle}
onPress={this.getClipboardContent}
>
PasteClipboard
</Button>
</View>
);
}
Theoutputisasshowninfollowingscreenshot:
Howitworks...
Inthisrecipe,webuiltasimplecopyandpasteapplicationbyusing
theClipboardAPIprovidedbyReactNative.TheClipboardmodulecurrentlyonly
supportscontentoftypeString,eventhoughthedevicescancopymore
complicateddata.Thismodulemakesusingtheclipboardaseasyascallingthe
methodssetStringandgetString.
Receivingpushnotifications
Inourever-connectedmobileworld,itisimportantforourappstoencourage
andenticeustoopenthem.Theyshouldcommunicatethatthereissomepiece
ofinformationwaitingforusifweopentheapp,thatwemustbeawareof
immediately.Thisiswheretheconceptofpushnotificationscomesin.Push
notificationshaveexistedsince2009oniOSand2010forAndroid,andare
frequentlyusedbyapplicationstolettheuserknowofachangethattheyshould
reactto.Thiscouldbeatimedurationprocessthathasfinished,oramessage
hasbeenposteddirectedattheuser;theusecasesareendless.
ThisrecipefocusessolelyonworkingwithpushnotificationsonbothiOSand
Android.WearegoingtoregisterourappwithAppleAPNSandGoogleCloud
Messaging(GCM)andsuccessfullyreceivepushnotifications.
Gettingready
Thisrecipeassumesyouhaveaworkingpushnotificationserverthat
communicateswithAPNSandGCM.Intherecipe,wewilloutputthedevice
tokenforyoutoregisterwithyourserver.
Forthisrecipe,wecreatedaReactNativeapplicationtitledPushNotifications.
Inthisrecipe,wewillusethereact-native-push-notificationslibrary.Toinstallit,
runthefollowingcommandintheterminalfromyourprojectrootdirectory:
$npminstallreact-native-push-notification--save
$react-nativelink
PushnotificationsforiOSrequireaphysicaldevice;youcannottestthisonasimulator.For
Android,yourAVDmusthaveGoogleAPIsinstalled.
Howtodoit...
1. BeforewebeginwritingReactNativecodetoreceivenotifications,we
havetoaddthepushnotificationsentitlementtoourAppID.OpentheiOS
ProjectinXcode.Theprojectfileislocatedintheios/directoryofyour
ReactNativeapplication(for
example,./PushNotifications/ios/PushNotifications.xcodeproj):
2. Beforeproceedingwiththeentitlementconfiguration,makesureyou
changeyourBundleIdentifiertobemeaningful.SelectthePROJECTinthe
projectnavigatorsidebar,thenselecttheapplicationtarget.UnderBundle
Identifier,changeittobemoremeaningful.Generallytheformat
iscom(org|net|...).companyname.appname:
3. PleaserefertothefollowingURLforaddingthepushnotification
entitlementandgeneratingacertificateforyourserver:
https://developer.apple.com/library/ios/documentation/IDEs/Conceptual/AppDistribut
ionGuide/AddingCapabilities/AddingCapabilities.html#//apple_ref/doc/uid/TP40012582
-CH26-SW6
4. NowthatourReactNativeappisreadytoreceiveApplepushnotifications,
let'swritesomecodetodoso.Openupyourindex.ios.jsfileandaddthe
followingimport:
importPushNotificationfrom'react-native-push-notification';
5. Next,weneedtoregisterforpushnotificationsand,inoursampleapp,we
willdisplaythemessageontheview,asfollows:
componentWillMount(){
this.setState({
notification:undefined
});
PushNotification.configure({
onRegister:function(token){
console.log('register',token);
},
onNotification:this.onNotificationReceived
});
}
onNotificationReceived=(notification)=>{
this.setState({
notification:notification.message
});
}
6. Now,ifyouwanttodisplaythenotificationmessagewhenitisreceived,
justaddthefollowingtoyourrendermethod:
<Text>{this.state.notification}</Text>
Thisishowoursampleapplooksrunningonadevice.
7. It'stimetomoveontoAndroid.Thispartisabitmoreinvolvedasthereis
currentlynoout-of-the-boxsupportfromReactNative.
8. FollowtheCreateanAPIprojecttogetaconfigurationfileandaddthe
configurationfiletoyourproject,ignoringtheGradleinstructions,from:
https://developers.google.com/cloud-messaging/android/client
BemakesuretostoreyourserverAPIkeyandsenderIDsomewherefor
easyretrieval.
9. GotothefollowingURLandfollowtheinstructionsonAndroid
installation:
https://github.com/zo0r/react-native-push-notification/blob/master/README.md#andro
id-installation
10. OnefeaturethatwemaywanttoimplementonAndroidthatalreadyexists
oniOSistoopenupourappwhentheusertapsanotification.
OpenAndroidManifest.xml(it'slocatedin./app/src/main)andaddthefollowing
toourMainActivitydefinition:
<intent-filter>
<actionandroid:name="OPEN_ACTIVITY_1"/>
<categoryandroid:name="android.intent.category.DEFAULT"/>
</intent-filter>
11. Whenyousendnotificationsfromyourserver,youhavetoensurethatyou
settheclick_actionto'OPEN_ACTIVITY_1',asfollows:
message.addNotification({
title:'Notification',
body:'ThanksforreadingReactNativeCookbook!',
icon:'ic_launcher',
click_action:'OPEN_ACTIVITY_1'
});
12. Nowthatwe'realldonesettingupourAndroidproject,wecanworkonthe
UI.Repeatstep4tostep6forindex.android.js.
13. ToregisterwiththeGCMservice,wemustmaketwochangesto
ourPushNotification.configuremethod.Addthefollowingtwoproperties:
senderID:'<SenderIDFromGoogle>',
requestPermissions:true
NowyoushouldbeabletosuccessfullyreceivepushnotificationsonAndroid
andopenyourapplicationwhenyoutapthenotification.
Howitworks...
Pushnotificationsareoneofthemoreadvancedrecipesthatwewillbecovering
inthischapter.Thelevelofdifficultyappliesmoretosettingupour
environments,ratherthanreactingtotheminourReactNativeapplication.Step
3andstep8focusonsettingupourdevelopmentservers.
OneimportantdistinctionbetweeniOSandAndroidistheactionthatoccurs
whentheusertapsonanotification.iOSdefaultstoopentheapplication,so,to
replicatethisfeature,weneededtoaddanintent-filter,asseeninstep11and
step12.Ifyoudonotwanttoauto-launchyourapplication,thenskipstep11and
donotsendtheclick_actioninyournotificationpayload.
AuthenticatingviatouchIDor
fingerprintsensor
Securityisaparamountconcerninsoftware,especiallywhenthereisanysortof
authentication.Breachesandleakedpasswordshavebecomeapartofthedaily
newscycle,andcompaniesofallsizesarewisinguptotheneedfor
implementingaddedsecuritymeasuresintheirapps.Onesuchmeasurein
mobiledevicesisbiometricauthentication,whichusesfingerprintscanningor
facerecognitiontechnologytoprovidesupplementaryidentificationmethods.
Thisrecipecovershowtoaddfingerprintscanningandfacerecognitionsecurity.
Thankstothereact-native-touch-idlibrary,thisprocesshasbeensimplifiedand
streamlinedinReactNativeappdevelopment.
Gettingready
Forthisrecipewe'llneedanewpureReactNativeapp.Let'scallitBiometricAuth.
We'llbeusingthereact-native-buttonandreact-native-touch-idlibraries.Installthem
withnpm:
npminstallreact-native-buttonreact-native-touch-id--save
Alternatively,wecanuseyarn:
yarnaddreact-native-buttonreact-native-touch-id
Onceinstalled,react-native-touch-idwillneedtobelinked,sobesuretofollowup
with:
react-nativelink
Permissionswillalsoneedtobeadjustedmanually.ForAndroidpermissions,
locatetheAndroidManifest.xmlfileintheproject,whichshouldbe
atHiddenContentApp/android/app/src/main/AndroidManifest.xml.Alongwiththeother
permissionsinthisfile,you'llneedtoaddthefollowing:
<uses-permissionandroid:name="android.permission.USE_FINGERPRINT"/>
ForiOSpermissions,you'llneedtoupdatetheInfo.plistfileinatexteditor.
Alongwithalltheotherentries,addthefollowing:
<key>NSFaceIDUsageDescription</key>
<string>EnablingFaceIDallowsyouquickandsecureaccesstoyouraccount.</string>
Howtodoit...
1. Let'sstartbyaddingdependenciestotheApp.jsfile,asfollows:
importReact,{Component}from'react';
import{
StyleSheet,
Text,
View
}from'react-native';
importButtonfrom'react-native-button';
importTouchIDfrom'react-native-touch-id';
2. Nextwe'lldefinethatAppclassandtheinitialstate.We'llkeeptrackofthe
authenticationstatusontheauthStatuspropertyofstate,asfollows:
exportdefaultclassAppextendsComponent{
state={
authStatus:null
}
//Definedinfollowingsteps
}
3. Let'sdefinetheauthenticatemethod,whichwillbefiredonbuttonpress,and
willinitiateauthenticationonthedevice.Wecaninitiateauthenticationby
executingtheTouchIDcomponent'sauthenticatemethod.Thismethod'sfirst
parameterisanoptionalstringexplainingthereasonfortherequest,as
follows:
authenticate=()=>{
TouchID.authenticate('Accesssecretinformation!')
.then(this.handleAuthSuccess)
.catch(this.handleAuthFailure);
}
4. ThismethodfiresthehandleAuthSuccessmethodonsuccess.Let'sdefineit
now.ThismethodsimplyupdatestheauthStatuspropertyofstatetothe
stringAuthenticated,asfollows:
handleAuthSuccess=()=>{
this.setState({
authStatus:'Authenticated'
});
}
5. Similarly,ifauthenticationfails,thehandleAuthFailurefunctionwillbecalled,
whichwillupdatethesamestate.authStatustothestringNotAuthenticated,as
follows:
handleAuthFailure=()=>{
this.setState({
authStatus:'NotAuthenticated'
});
}
6. Therendermethodwillneedabuttontoinitiatetheauthenticationrequest,
andtwoTextcomponents:oneforalabel,andonetodisplaythe
authenticationstatus,asfollows:
render(){
return(
<Viewstyle={styles.container}>
<Button
containerStyle={styles.buttonContainer}
style={styles.button}
onPress={this.authenticate}>
Authenticate
</Button>
<Textstyle={styles.label}>AuthenticationStatus</Text>
<Textstyle={styles.welcome}>{this.state.authStatus}</Text>
</View>
);
}
7. Finally,we'lladdstylestocolor,size,andlayouttheUI,asfollows:
conststyles=StyleSheet.create({
container:{
flex:1,
justifyContent:'center',
alignItems:'center',
backgroundColor:'#fff',
},
welcome:{
fontSize:20,
textAlign:'center',
margin:10,
},
label:{
textAlign:'center',
color:'#333333',
marginBottom:5,
},
buttonContainer:{
width:150,
padding:10,
margin:5,
height:40,
overflow:'hidden',
backgroundColor:'#FF5722'
},
button:{
fontSize:16,
color:'white'
}
});
Howitworks...
Thisrecipehasillustratedhowsimpleitistoincorporatenativefingerprintand
facialrecognitionsecurityintoaReactNativeapp.Thecall
toTouchID.authenticatealsotakesasecond,optionaloptionsobjectparameterwith
threeproperties:titleforthetitleoftheconfirmationdialog(Android
only),colorforthecolorofthedialog(Androidonly),andafallbackLabelfor
editingthedefaultShowPasswordlabel(iOSonly).
Hidingapplicationcontentwhen
multitasking
Keepingthethemeofapplicationsecuritygoing,wehavetobewarysometimes
ofunwantedeyesandhandstouchingourdevicesandpotentiallygettingaccess
toourapplications.Inordertoprotecttheuserfrompryingeyeswhilelookingat
sensitiveinformation,wecanmaskourapplicationwhentheapplicationis
hidden,butstillactive.Oncetheuserreturnstotheapplication,wewouldsimply
removethemaskandtheusercancontinueusingtheappasnormal.Agood
examplewouldbetheChasebankingapplication;ithidesyoursensitivebanking
informationwhentheapplicationisnotintheforeground.
Thisrecipewillshowyouhowtorenderanimagetomaskyourapplicationand
removeitoncetheapplicationreturnstotheforegroundoractivestate.Wewill
coverbothiOSandAndroid;however,theimplementationvariesinitsentirety.
ForiOS,weemployapureObjective-Cimplementationforoptimal
performance.ForAndroid,we'regoingtohavetomakesomemodificationsto
theMainActivityinordertosendaneventtoourJavaScriptlayerthatthe
applicationhaslostfocus.Wewillhandletherenderingoftheimagemaskthere.
Gettingready
We'regoingtoneedanimagehandytouseasthemaskwhentheappisnot
foregrounded.IchosetouseaniPhonewallpaper,whichyoucanfindat:
http://www.hdiphone7wallpapers.com/2016/09/white-squares-iphone-7-and-7-plus-wallpapers.ht
ml
Theimageisasortofstylizedmosaicpattern.Itlookslikethis.
Youcanofcourseusewhateverimageyou'dlike.Inthisrecipe,theimagefile
willbenamedhidden.jpg,sorenameyourimageaccordingly.
We'llneedanewpureReactNativeapp.Let'scallitHiddenContentApp.
Howtodoit...
1. Let'sbeginbyaddingthemaskimagetotheiOSportionoftheapp.We'll
needtoopentheiosfolderoftheprojectinXcode,locatedin
theios/directoryofthenewReactNativeapp.
2. Wecanaddthehidden.jpgimagetotheprojectbydragginganddroppingthe
imageintotheImages.xcassetsfolderoftheprojectinXcode,asshowninthis
screenshot.
3. Nextwe'lladdanewimplementationandtwomethodsto
theAppDelegate.mfile.Theentiretyofthefilecanbefoundasfollows,
includinggeneratedcode.Thecodewe'readdingismarkedinboldfor
clarity.We'reextendingtheapplicationWillResignActivemethod,whichwillfire
wheneveragivenappchangesfrombeingforegrounded,toadd
animageViewwiththehidden.jpgasitsimage.Similarly,wealsoneedtoextend
theoppositemethod,applicationDidBecomeActive,toremovetheimagewhen
theappisre-foregrounded:
#import"AppDelegate.h"
#import<React/RCTBundleURLProvider.h>
#import<React/RCTRootView.h>
@implementationAppDelegate{
UIImageView*imageView;
}
-(BOOL)application:(UIApplication*)applicationdidFinishLaunchingWithOptions:(NSDictionary*)launchOptions
{
NSURL*jsCodeLocation;
jsCodeLocation=[[RCTBundleURLProvidersharedSettings]jsBundleURLForBundleRoot:@"index"fallbackResource:nil];
RCTRootView*rootView=[[RCTRootViewalloc]initWithBundleURL:jsCodeLocation
moduleName:@"HiddenContentApp"
initialProperties:nil
launchOptions:launchOptions];
rootView.backgroundColor=[[UIColoralloc]initWithRed:1.0fgreen:1.0fblue:1.0falpha:1];
self.window=[[UIWindowalloc]initWithFrame:[UIScreenmainScreen].bounds];
UIViewController*rootViewController=[UIViewControllernew];
rootViewController.view=rootView;
self.window.rootViewController=rootViewController;
[self.windowmakeKeyAndVisible];
returnYES;
}
-(void)applicationWillResignActive:(UIApplication*)application{
imageView=[[UIImageViewalloc]initWithFrame:[self.windowframe]];
[imageViewsetImage:[UIImageimageNamed:@"hidden.jpg"]];
[self.windowaddSubview:imageView];
}
-(void)applicationDidBecomeActive:(UIApplication*)application{
if(imageView!=nil){
[imageViewremoveFromSuperview];
imageView=nil;
}
}
@end
4. Withthepreviousthreesteps,alloftheworkrequiredfordisplayingthe
maskintheiOSappiscomplete.Let'smoveontotheAndroidportionby
openingtheAndroidportionoftheprojectinAndroidStudio.InAndroid
Studio,selectOpenanexistingAndroidStudioprojectandopen
theandroiddirectoryoftheproject.
5. Theonlynativecodewe'llneedtoupdateintheAndroidprojectlives
inMainActivity.java,locatedhere:
We'llneedtoaddonemethod,aswellasthethreeimportsfromReactthe
methoduses.Again,thecompleteMainActivity.javafileisbelow,withaddedcode
markedinbold.We'redefininganonWindowFocusChangedmethodthatextendsthe
basemethod'sfunctionality.ThebaseonWindowFocusChangedAndroidmethodisfired
wheneveragivenapp'sfocushaschanged,passingwithitahasFocusBoolean
representingwhethertheapphasfocusornot.Ourextensionwilleffectively
passthathasFocusBooleanfromtheparentmethoddowntotheReactNativelayer
viaaneventwe'renamingfocusChange,asfollows:
packagecom.hiddencontentapp;
importcom.facebook.react.ReactActivity;
importcom.facebook.react.bridge.Arguments;
importcom.facebook.react.bridge.WritableMap;
importcom.facebook.react.modules.core.DeviceEventManagerModule;
publicclassMainActivityextendsReactActivity{
/**
*ReturnsthenameofthemaincomponentregisteredfromJavaScript.
*Thisisusedtoschedulerenderingofthecomponent.
*/
@Override
protectedStringgetMainComponentName(){
return"HiddenContentApp";
}
@Override
publicvoidonWindowFocusChanged(booleanhasFocus){
super.onWindowFocusChanged(hasFocus);
if(getReactNativeHost().getReactInstanceManager().getCurrentReactContext()!=null){
WritableMapparams=Arguments.createMap();
params.putBoolean("appHasFocus",hasFocus);
getReactNativeHost().getReactInstanceManager()
.getCurrentReactContext()
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
.emit("focusChange",params);
}
}
}
6. Tousethehidden.jpgmaskimageinAndroid,we'llneedtoalsoadditto
theReactNativeproject.Let'screateanewassetsfolderintherootofthe
ReactNativeproject,andaddthehidden.jpgimagefiletothenewfolder.
7. Withthenativepiecesinplace,we'rereadytoturntotheJavaScriptportion
oftheapp.Let'saddtheimportswe'llbeusingtoApp.js,asfollows:
importReact,{Component}from'react';
import{
StyleSheet,
Text,
View,
DeviceEventEmitter,
Image
}from'react-native';
8. Next,let'screatetheAppclassandtheinitialstate.Thestatewillonlyneed
ashowMaskBoolean,whichwilldictateifthemaskshouldbedisplayed,as
follows:
exportdefaultclassAppextendsComponent{
state={
showMask:null
}
//Definedinfollowingsteps
}
9. Whenthecomponentmounts,wewanttoregisteraneventlistenertolisten
toeventsemittedfromthenativeAndroidlayerusing
theDeviceEventEmitter'saddListenermethod,passingthestringfocusChangeasthe
nameoftheeventtolistenforasthefirstparameter,andacallbackto
executeasthesecondparameter.Asyoumayrecall,focusChangeisthename
weassignedtheeventinMainActivity.javaintheonWindowFocusChangemethod
instep5.Registertheeventlistenerasfollows:
componentWillMount(){
this.subscription=DeviceEventEmitter.addListener(
'focusChange',
this.onFocusChange
);
}
10. Inthisstepwesavedtheeventlistenertotheclassmemberthis.subscription.
Thiswillallowfortheeventlistenertobecleaneduponcethecomponent
isunmounted.Weachievethisbysimplycallingtheremovemethod
onthis.subscriptionwhenthecomponentunmounts,via
thecomponentWillUnmountlifecyclehook,asfollows:
componentWillUnmount(){
this.subscription.remove();
}
11. Let'sdefinetheonFocusChangehandlerusedinstep9.Themethodreceives
aparamsobjectwithanappHasFocusBooleanthat'sbeenpassedfromthenative
layerviatheonWindowFocusChangedmethoddefinedinstep5.Bysetting
theshowMaskBooleanonstatetotheinverseoftheappHasFocusBoolean,we
canusethatintherenderfunctiontotoggledisplayingthehidden.jpgimage,
asfollows:
onFocusChange=(params)=>{
this.setState({showMask:!params.appHasFocus})
}
12. Therendermethod'smaincontentisnotimportantinthisrecipe,butwecan
useittoapplythehidden.jpgmaskimagewhentheshowMaskpropertyonstate
istrue,asfollows:
render(){
if(this.state.showMask){
return(<Imagesource={require('./assets/hidden.jpg')}/>);
}
return(
<Viewstyle={styles.container}>
<Textstyle={styles.welcome}>WelcometoReactNative!</Text>
</View>
);
}
13. Theappiscomplete.Oncetheappisloaded,youshouldbeabletogotothe
appselectionview(doublepressinghomeoniOS,orthesquarebuttonon
Android)andseethemaskimageappliedtotheappwhenitisnot
foregrounded.NotethatAndroidemulatorsmaynotproperlyapplythe
maskasexpected,sothisfeaturemightrequireanAndroiddevicefor
testing.
Howitworks...
Inthisrecipewe'veseenanexampleofhavingtousetwoseparateapproaches
foraccomplishingthesametask.ForiOS,wehandleddisplayingtheimage
maskexclusivelyinthenativelayer,withoutanyneedfortheReactNativelayer.
ForAndroid,weusedReactNativetohandletheimagemasking.
Instep3weextendedtwoObjective-Cmethods:applicationWillResignActive,which
fireswhenanappchangesfrombeingforegrounded,
andapplicationDidBecomeActive,whichfireswhentheappisforegrounded.Foreach
event,wesimplytoggleanimageViewthatdisplaysthehidden.jpgimagestorein
theImages.xcassettesfolderintheXcodeproject.
Instep5weusedtheReactclassRCTDeviceEventEmitterfrom
theDeviceEventManagerModuletoemitaneventnamedfocusChange,passingalong
aparamsobjectwiththeappHasFocusbooleantotheReactNativelayer,asfollows:
getReactNativeHost().getReactInstanceManager()
.getCurrentReactContext()
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
.emit("focusChange",params);
}
Instep9wedefinedthecomponentWillMountlifecyclehook,whichsetsupanevent
listenerforthisfocusChangeeventthatwillbeemittedfromthenativeAndroid
layer,firingtheonFocusChangemethod,whichwillupdatethevalue
ofstate'sshowMaskvaluebasedonthenativeappHasFocusvalue,triggeringarerender,
displayingthemaskasappropriate.
BackgroundprocessingoniOS
Overthelastseveralyears,processingpowerinmobiledeviceshasincreased
considerably.Usersaredemandingricherexperiencesandonemethodof
achievingimprovedperformanceonmodernmobiledevicesisvia
multithreading.Mostmobiledevicestodayarepoweredbymulticore
processors,andtheiroperatingsystemsnowofferdeveloperseasyabstractions
forexecutingcodeinthebackground,withoutinterferingwiththeperformance
oftheapp'sUI.
ThisrecipewillcoverboththeuseofiOS'sGrandCentralDispatch(GCD)to
executeasynchronousbackgroundprocessingonanewthread,and
communicatingbacktotheReactNativelayerwhentheprocessingiscomplete.
Gettingready
Forthisrecipe,we'llneedanewpureReactNativeapplication.Let'sname
itMultiThreadingApp.
We'llalsobeusingthereact-native-buttonlibrary.Installitwithnpm:
npminstallreact-native-button--save
Alternatively,wecanuseyarn:
yarnaddreact-native-button--save
Howtodoit...
1. We'llstartbyopeningtheiOSProjectinXcode,locatedintheiosdirectory
ofthenewReactNativeapp.
2. Let'saddanewCocoaclassfilenamedBackgroundTaskManagerof
subclassNSObject.RefertotheExposingCustomiOSModulesrecipeinthis
chapterformoredetailsondoingthisinXcode.
3. Next,letswirethenewmoduletotheReactRCTBrideModuleinthenew
module'sheaderfile,BackgroundTaskManager.h.Thecodetobeaddedismarked
inboldinthefollowingsnippet:
#import<Foundation/Foundation.h>
#import<dispatch/dispatch.h>
#import"RCTBridgeModule.h"
@interfaceBackgroundTaskManager:NSObject<RCTBridgeModule>{
dispatch_queue_tbackgroundQueue;
}
@end
4. We'llimplementthenativemoduleintheBackgroundTaskManager.mfile.Again,
thenewcodewe'readdingismarkedinboldinthefollowingsnippet:
#import"BackgroundTaskManager.h"
#import"RCTBridge.h"
#import"RCTEventDispatcher.h"
@implementationBackgroundTaskManager
@synthesizebridge=_bridge;
RCT_EXPORT_MODULE();
RCT_EXPORT_METHOD(loadInBackground){
backgroundQueue=dispatch_queue_create("com.moduscreate.bgqueue",NULL);

dispatch_async(backgroundQueue,^{
NSLog(@"processingbackground");
[self.bridge.eventDispatchersendAppEventWithName:@"backgroundProgress"body:@{@"status":@"Loading"}];
[NSThreadsleepForTimeInterval:5];
NSLog(@"slept");
dispatch_async(dispatch_get_main_queue(),^{
NSLog(@"Doneprocessing;mainthread");
[self.bridge.eventDispatchersendAppEventWithName:@"backgroundProgress"body:@{@"status":@"Done"}];
});
});
}
@end
5. Let'sturntotheJavaScriptlayernext.We'llstartbyaddingdependenciesto
theApp.jsfile.Aspartofthedependencies,wewillalsoneedtoimport
theBackgroundTaskManagernativemodulethatwedefinedinstep3andstep4,
asfollows:
importReact,{Component}from'react';
import{
StyleSheet,
Text,
View,
NativeModules,
NativeAppEventEmitter
}from'react-native';
importButtonfrom'react-native-button';
constBackgroundTaskManager=NativeModules.BackgroundTaskManager;
6. Let'sdefinetheAppclass,withaninitialstateofbackgroundTaskStatussettothe
stringNotStarted,andadoNothingCountpropertyinitializedto0,asfollows:
exportdefaultclassAppextendsComponent{
state={
backgroundTaskStatus:'NotStarted',
counter:0
}
//Definedinfollowingsteps
}
7. We'llneedtolistentothebackgroundProcesseventthatwillbeemittedfrom
thenativeiOSlayerfromthecustommodulewecreatedinstep3andstep
4.Let'ssetupaneventlistenerusingtheNativeAppEventEmitterReactNative
component,whichsetsthebackgroundTaskStatuspropertyofstatetothevalue
ofstatusontheeventobjectreceivedfromthenativeevent,asfollows:
componentWillMount=()=>{
this.subscription=NativeAppEventEmitter.addListener(
'backgroundProgress',
event=>this.setState({backgroundTaskStatus:event.status})
);
}
8. Whenthecomponentunmounts,weneedtoremovetheeventlistenerfrom
thepreviousstep,asfollows:
componentWillUnmount=()=>{
this.subscription.remove();
}
9. TheUIwillhavetwobuttonsthatwilleachneedamethodtocallwhen
pressed.TherunBackgroundTaskwillruntheloadInBackgroundmethodthatwe
definedandexportedfromthenativeiOSlayeron
theBackgroundTaskManagercustomnativemodule.TheincreaseCounterbuttonwill
simplyincreasethecounterpropertyonstateby1,servingtoshowhowthe
mainthreadisnotblocked,asfollows:
runBackgroundTask=()=>{
BackgroundTaskManager.loadInBackground();
}
increaseCounter=()=>{
this.setState({
counter:this.state.counter+1
});
}
10. TheUIoftheappwillconsistoftwobuttonstoshowthebuttons
andTextcomponentsfordisplayingthevaluessavedonstate.TheRun
TaskbuttonwillexecutetherunBackgroundTaskmethodtokickoffa
backgroundprocess,andthis.state.backgroundTaskStatuswillupdatetodisplay
anewstatusfortheprocess.Forthefivesecondsthatthebackground
processisrunning,pressingtheIncreaseCounterbuttonwillstillincrease
thecounterby1,demonstratingthatthebackgroundprocessisnon-
blocking,asshowninthefollowingsnippet:
render(){
return(
<Viewstyle={styles.container}>
<Button
containerStyle={styles.buttonContainer}
style={styles.buttonStyle}
onPress={this.runBackgroundTask}>
RunTask
</Button>
<Textstyle={styles.instructions}>
BackgroundTaskStatus:
</Text>
<Textstyle={styles.welcome}>
{this.state.backgroundTaskStatus}
</Text>
<Textstyle={styles.instructions}>
Pressing"IncreaseConter"buttonshowsthatthetaskis
notblockingthemainthread
</Text>
<Button
containerStyle={[
styles.buttonContainer,
styles.altButtonContainer
]}
style={styles.buttonStyle}
onPress={this.increaseCounter}
>
IncreaseCounter
</Button>
<Textstyle={styles.instructions}>
CurrentCount:
</Text>
<Textstyle={styles.welcome}>
{this.state.counter}
</Text>
</View>
);
}
11. Asafinalstep,let'slayoutandstyletheappwiththestylesblock,as
follows:
conststyles=StyleSheet.create({
container:{
flex:1,
justifyContent:'center',
alignItems:'center',
backgroundColor:'#F5FCFF',
},
welcome:{
fontSize:20,
textAlign:'center',
margin:10,
},
instructions:{
textAlign:'center',
color:'#333333',
marginBottom:5,
marginLeft:20,
marginRight:20
},
buttonContainer:{
width:150,
padding:10,
margin:5,
height:40,
overflow:'hidden',
borderRadius:4,
backgroundColor:'#FF5722'
},
altButtonContainer:{
backgroundColor:'#CDDC39',
marginTop:30
},
buttonStyle:{
fontSize:16,
color:'white'
}
});
Howitworks...
Inthisrecipe,wecreatedanativemodulesimilartothemodulecoveredin
theExposingcustomiOSmodulesrecipefromearlierinthischapter.Wedefined
thenativemoduletoperformarbitraryexecutioninthebackgroundoftheReact
Nativeapp.Inthisrecipethebackgroundprocessismadeupofthefollowing
threesteps:
1. Spawnanewthread.
2. Sleepforfivesecondsonthenewthread.
3. Afterthefivesecondsleep(simulatingtheendofarunningbackground
process),aneventisdispatchedfromtheiOSlayertotheReactNative
layer,lettingitknowthattheprocesshasbeencompleted.Thisis
accomplishedviatheOS'sGCDAPI.
ThepurposeoftheUIinthisappistoexhibitthatmultithreadinghasbeen
achieved.IfthebackgroundprocesswasexecutedintheReactNativelayer,due
toJavaScript'ssingle-threadednature,theappwouldhavelockedupforfive
secondswhilethatprocesswasrunning.Whenyoupressabutton,thebridgeis
invoked,whereuponmessagescanbepostedtothenativelayer.Ifthenative
threadiscurrentlybusysleeping,thenwecannotprocessthismessage.By
offloadingthatprocessingtoanewthread,bothcanbeexecutedatthesame
time.
BackgroundprocessingonAndroid
Inthisrecipewe'llbebuildingoutanAndroidequivalenttothepreviousrecipe.
ThisrecipewillalsousethenativeAndroidlayertocreateanewprocess,keep
thatprocessrunningbysleepingforfiveseconds,andallowuserinteractionvia
thebuttontoexhibitthattheapp'smainprocessingthreadisnotblocked.
Whiletheendresultwillbeverymuchthesame,spawninganewprocessinan
AndroidprojectishandledabitdifferentlyfromiOS.Thisrecipewillmakeuse
ofthenativeAsyncTaskfunction,specializedforhandlingshort-running
backgroundprocesses,toallowexecutionintheReactNativelayerwithout
blockingthemainthread.
Gettingready
Forthisrecipewe'llneedtocreateanewpureReactNativeapp.Let'sname
itMultiThreadingApp.
Wewillalsobeusingthereact-native-buttonlibrary.Installitwithnpm:
npminstallreact-native-button--save
Alternatively,wecanuseyarn:
npminstallreact-native-button--save
Howtodoit...
1. Let'sstartbyopeningtheAndroidprojectinAndroidStudio.InAndroid
Studio,selectOpenanexistingAndroidStudioprojectandopen
theandroiddirectoryofthenewproject.
2. We'llneedtwonewJava
classes:BackgroundTaskManagerandBackgroundTaskPackage.
3. Nowthatbothclasseshavebeencreated,let's
openBackgroundTaskManager.javaandbeginimplementingthenativemodule
thatwillwrapanAsyncTaskoperation,startingwithimportsanddefiningthe
class.Furthermore,likeanyothernativeAndroidmodule,we'llneedto
definethegetNamemethod,usedtoprovideReactNativewithanameforthe
module,asfollows:
packagecom.multithreadingapp;
importandroid.os.AsyncTask;
importcom.facebook.react.bridge.Arguments;
importcom.facebook.react.bridge.ReactApplicationContext;
importcom.facebook.react.bridge.ReactContextBaseJavaModule;
importcom.facebook.react.bridge.ReactMethod;
importcom.facebook.react.bridge.WritableMap;
importcom.facebook.react.modules.core.DeviceEventManagerModule;
publicclassBackgroundTaskManagerextendsReactContextBaseJavaModule{
publicBackgroundTaskManager(ReactApplicationContextreactApplicationContext){
super(reactApplicationContext);
}
@Override
publicStringgetName(){
return"BackgroundTaskManager";
}
//Definedinfollowingsteps
}
4. InordertoexecuteanAsyncTask,itneedstobesubclassedbyaprivateclass.
We'llneedtoaddanewprivateinnerBackgroundLoadTasksubclassforthis.
Beforewedefineit,let'sfirstaddaloadInBackgroundmethodthatwill
ultimatelybeexportedtotheReactNativelayer.Thismethodsimply
createsanewinstanceofBackgroundLoadTaskandcallsitsexecutemethod,as
follows:
publicclassBackgroundTaskManagerextendsReactContextBaseJavaModule{
//Definedinpreviousstep
@ReactMethod
publicvoidloadInBackground(){
BackgroundLoadTaskbackgroundLoadTask=newBackgroundLoadTask();
backgroundLoadTask.execute();
}
}
5. TheBackgroundLoadTasksubclasswillalsobeusingahelperfunctionfor
sendingeventsbackandforthacrosstheReactNativebridgeto
communicatethestatusofthebackgroundprocess.ThesendEventmethod
takesaneventNameandparamsasarguments,thenusesReact
Native'sRCTDeviceEventEmitterclasstoemittheevent,asfollows:
publicclassBackgroundTaskManagerextendsReactContextBaseJavaModule{
//Definedinstepsabove
privatevoidsendEvent(StringeventName,WritableMapparams){
getReactApplicationContext().getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class).emit(eventName,params);
}
}
6. Nowlet'smoveontodefiningtheBackgroundLoadTasksubclass,which
extendsAsyncTask.Thesubclasswillbemadeupofthree
methods:doInBackgroundforspinningupanewthreadandsleepingitforfive
minutes,onProgressUpdateforsendinga"Loading"statustotheReactNative
layer,andonPostExecuteforsendinga"Done"statuswhenthebackgroundtask
hascompleted,asfollows:
publicclassBackgroundTaskManagerextendsReactContextBaseJavaModule{
//Definedinabovesteps
privateclassBackgroundLoadTaskextendsAsyncTask<String,String,String>{
@Override
protectedStringdoInBackground(String...params){
publishProgress("Loading");
try{
Thread.sleep(5000);
}catch(Exceptione){
e.printStackTrace();
}
return"Done";
}
@Override
protectedvoidonProgressUpdate(String...values){
WritableMapparams=Arguments.createMap();
params.putString("status","Loading");
sendEvent("backgroundProgress",params);
}
@Override
protectedvoidonPostExecute(Strings){
WritableMapparams=Arguments.createMap();
params.putString("status","Done");
sendEvent("backgroundProgress",params);
}
}
}
7. SincetheonlydifferencebetweentheiOSimplementationandtheAndroid
implementationlivesinthenativelayeroftherecipe,youcanfollowstep5
tostep11ofthepreviousrecipetoimplementtheJavaScriptportionofthe
app.
8. Thefinalappshouldbehaveandlook(asidefromdifferencesindevices)
thesameastheappinthepreviousrecipe.
Howitworks...
Inthisrecipe,wemimickedthefunctionalitywecreatedintheBackground
processingoniOSrecipeonAndroid.WecreatedanAndroidnativemodule
withamethodwhich,wheninvoked,performsarbitraryexecutioninthe
background(sleepforfiveseconds).Whentheprocessiscomplete,itemitsan
eventtotheReactNativelayer,whereuponweupdatetheappUItoreflectthe
statusofthebackgroundprocess.Androidhasmultipleoptionsforperforming
multithreadedoperationsnatively.Inthisrecipe,weusedAsyncTask,sinceitis
gearedtowardsshort-running(severalseconds)processes,itisrelativelysimple
toimplement,andtheoperatingsystemmanagesthreadcreationandresource
allocationforus.YoucanreadmoreaboutAsyncTaskintheofficialdocumentation
at:
https://developer.android.com/reference/android/os/AsyncTask
PlayingaudiofilesoniOS
InthechapterImplementingComplexUserInterfaces–PartIII,wecovered
buildingoutarelativelysophisticatedlittleaudioplayerintheCreatinganAudio
PlayerrecipeusingtheAudiocomponentprovidedbytheExpoSDK.Oneofthe
shortcomingofExpo'sAudiocomponent,however,isthatitcannotbeusedto
playaudiowhentheappisbackgrounded.Usingthenativelayeriscurrentlythe
onlywaytoachievethis.
Inthisrecipe,wewillcreateanativemoduletoshowtheiOSMediaPickerand
thenselectamusicfiletoplay.TheselectedfilewillplaythroughthenativeiOS
mediaplayer,whichallowsaudiotobeplayedwhentheappisbackgrounded,
andallowstheusertocontroltheaudioviathenativeiOScontrolcenter.
Gettingready
Forthisrecipe,we'llneedtocreateanewpureReactNativeapp.Let'scall
itAudioPlayerApp.
We'llalsobeusingthereact-native-buttonlibrary,whichcanbeinstalledwithnpm:
npminstallreact-native-button--save
Alternatively,wecanuseyarn:
yarnaddreact-native-button
Thisisarecipethatshouldonlybeexpectedtoworkonarealdevice.You'llalso
wanttomakesureyouhavemusicsyncedtotheiOSdeviceandavailableinthe
medialibrary.
Howtodoit...
1. Let'sstartbyopeningtheiOSProjectinXcodelocatedintheiosdirectory
ofthenewReactNativeapp.
2. Next,we'llcreateanewObjective-CCocoaclasscalledMediaManager.
3. IntheMediaManagerheader(.h)file,weneedto
importMPMediaPickerControllerandMPMusicPlayerController,alongwiththeReact
Nativebridge(RCTBridgeModule),asfollows:
#import<Foundation/Foundation.h>
#import<MediaPlayer/MediaPlayer.h>
#import<React/RCTBridgeModule.h>
#import<React/RCTEventDispatcher.h>
@interfaceMediaManager:NSObject<RCTBridgeModule,MPMediaPickerControllerDelegate>
@property(nonatomic,retain)MPMediaPickerController*mediaPicker;
@property(nonatomic,retain)MPMusicPlayerController*musicPlayer;
@end
4. First,wearegoingtoneedtoworkonaddingthenativeMediaPickerin
theMediaManagerimplementation(MediaManager.m).Thefirstmethodswillbefor
showingandhidingtheMediaPicker:showMediaPickerandhideMediaPicker,as
follows:
#import"MediaManager.h"
#import"AppDelegate.h"
@implementationMediaManager
RCT_EXPORT_MODULE();
@synthesizebridge=_bridge;
@synthesizemusicPlayer;
#pragmamarkprivate-methods
-(void)showMediaPicker{
if(self.mediaPicker==nil){
self.mediaPicker=[[MPMediaPickerControlleralloc]initWithMediaTypes:MPMediaTypeAnyAudio];

[self.mediaPickersetDelegate:self];
[self.mediaPickersetAllowsPickingMultipleItems:NO];
[self.mediaPickersetShowsCloudItems:NO];
self.mediaPicker.prompt=@"Selectsong";
}

AppDelegate*delegate=(AppDelegate*)[[UIApplicationsharedApplication]delegate];

[delegate.window.rootViewControllerpresentViewController:self.mediaPickeranimated:YEScompletion:nil];
}
voidhideMediaPicker(){
AppDelegate*delegate=(AppDelegate*)[[UIApplicationsharedApplication]delegate];
[delegate.window.rootViewControllerdismissViewControllerAnimated:YEScompletion:nil];
}
//Definedonfollowingsteps
@end
5. Next,we'llimplementthetwoactionsthat
themediaPickerneeds:didPickMediaItemsforpickingamediaitem,
andmediaPickerDidCancelforcancellingtheaction,asfollows:
-(void)mediaPicker:(MPMediaPickerController*)mediaPickerdidPickMediaItems:(MPMediaItemCollection*)mediaItemCollection{
MPMediaItem*mediaItem=mediaItemCollection.items[0];
NSURL*assetURL=[mediaItemvalueForProperty:MPMediaItemPropertyAssetURL];

[self.bridge.eventDispatchersendAppEventWithName:@"SongPlaying"
body:[mediaItemvalueForProperty:MPMediaItemPropertyTitle]];

if(musicPlayer==nil){
musicPlayer=[MPMusicPlayerControllersystemMusicPlayer];
}

[musicPlayersetQueueWithItemCollection:mediaItemCollection];
[musicPlayerplay];

hideMediaPicker();
}
-(void)mediaPickerDidCancel:(MPMediaPickerController*)mediaPicker{
hideMediaPicker();
}
6. Next,we'regoingtoneedtoexposeourMediaManagertotheReactNative
bridgeandcreateamethodthatwillbeinvokedtoshowtheMediaPicker,as
follows:
RCT_EXPORT_MODULE();
RCT_EXPORT_METHOD(showSongs){
[selfshowMediaPicker];
}
7. We'rereadytomoveontotheJavaScriptportion.Let'sstartbyadding
dependenciestoApp.js.WealsoneedtoimporttheMediaManagernative
modulewecreatedinstep3tostep6usingtheNativeModulescomponent,as
follows:
importReact,{Component}from'react';
import{
StyleSheet,
Text,
View,
NativeModules,
NativeAppEventEmitter
}from'react-native';
importButtonfrom'react-native-button';
constMediaManager=NativeModules.MediaManager;
8. Let'sdefinetheAppclassandtheinitialstate.ThecurrentSongpropertywill
holdthetrackinfoforthecurrentlyplayingsong,aspassedfromthenative
layer,asfollows:
exportdefaultclassAppextendsComponent{
state={
currentSong:null
}
//Definedonfollowingsteps
}
9. Whenthecomponentmounts,we'llsubscribetotheSongPlayingeventthat
willbeemittedfromthenativelayerwhenasongbeginsplaying.We'll
savetheeventlistenertoalocalsubscriptionclassvariablesothatwecan
cleanitupwiththeremovemethodwhenthecomponentunmounts,as
follows:
componentWillMount(){
this.subscription=NativeAppEventEmitter.addListener(
'SongPlaying',
this.updateCurrentlyPlaying
);
}
componentWillUnmount=()=>{
this.subscription.remove();
}
10. We'llalsoneedamethodforupdatingthecurrentSongvalueonstate,anda
methodforcallingtheshowSongsmethodonthenativeMediaManagermodulewe
definedinstep3tostep6,asfollows:
updateCurrentlyPlaying=(currentSong)=>{
this.setState({currentSong});
}
showSongs(){
MediaManager.showSongs();
}
11. TherendermethodwillbemadeupofaButtoncomponentforexecuting
theshowSongsmethodwhenpressed,andTextcomponentsfordisplayingthe
infoforthesongthat'scurrentlyplaying,asfollows:
render(){
return(
<Viewstyle={styles.container}>
<Button
containerStyle={styles.buttonContainer}
style={styles.buttonStyle}
onPress={this.showSongs}>
PickSong
</Button>
<Textstyle={styles.instructions}>SongPlaying:</Text>
<Textstyle={styles.welcome}>{this.state.currentSong}</Text>
</View>
);
}
12. Finally,we'lladdourstylesforlayingoutandstylingtheapp,asfollows:
conststyles=StyleSheet.create({
container:{
flex:1,
justifyContent:'center',
alignItems:'center',
backgroundColor:'#F5FCFF',
},
welcome:{
fontSize:20,
textAlign:'center',
margin:10,
},
instructions:{
textAlign:'center',
color:'#333333',
marginBottom:5,
},
buttonContainer:{
width:150,
padding:10,
margin:5,
height:40,
overflow:'hidden',
borderRadius:4,
backgroundColor:'#3B5998'
},
buttonStyle:{
fontSize:16,
color:'#fff'
}
});
Howitworks...
InthisrecipewecoveredhowtousetheMediaPlayeriniOSbywrappingits
functionalityinanativemodule.Themediaplayerframeworkallowsusto
accessthenativeiPodlibrary,andplayaudiofilesfromthelibraryonthedevice
usingthesamefunctionalityasthenativeiOSMusicapp.
PlayingaudiofilesonAndroid
AbenefitthatGooglelikestoclaimthatAndroidhasoveriOS,isflexibilityin
dealingwithfilestorage.AndroiddevicessupportexternalSDcardsthatcan
befilledwithmediafiles,anddonotneedaproprietarymethodofadding
multimediaasiOSdoes.
Inthisrecipe,wewilluseAndroid'snativeMediaPicker,whichisstartedfroman
intent.Wewillthenbeabletopickasongandhaveitplaythroughour
application.
Gettingready
Forthisrecipe,wecreatedaReactNativeapplicationtitledAudioPlayer.
Inthisrecipe,wewillusethereact-native-buttonlibrary.Toinstallit,runthe
followingcommandintheterminalfromyourprojectrootdirectory:
$npminstallreact-native-button--save
MakesureyouhavemusicfilesavailableinyourMusic/directoryonyour
Androiddeviceoremulator.
Howtodoit...
1. Let'sstartbyopeningtheAndroidprojectusingAndroidStudio.InAndroid
Studio,selectOpenanexistingAndroidStudioprojectandopen
theandroiddirectoryoftheproject.
2. We'llneedtwonewJavaclassesforthisrecipe:MediaManagerandMediaPackage.
3. OurMediaManagerwilluseintentstoshowthemediaPicker,MediaPlayertoplay
music,andMediaMetadataRetrievertoparsemetadatainformationfromthe
audiofiletosendbacktotheJavaScriptlayer.Let'sstartbyimportingallof
thedependencieswe'llneedintheMediaManager.javafile,asfollows:
importandroid.app.Activity;
importandroid.content.Intent;
importandroid.media.AudioManager;
importandroid.media.MediaMetadataRetriever;
importandroid.media.MediaPlayer;
importandroid.net.Uri;
importandroid.provider.MediaStore;
importcom.facebook.react.bridge.ActivityEventListener;
importcom.facebook.react.bridge.Arguments;
importcom.facebook.react.bridge.ReactApplicationContext;
importcom.facebook.react.bridge.ReactContextBaseJavaModule;
importcom.facebook.react.bridge.ReactMethod;
importcom.facebook.react.bridge.WritableMap;
importcom.facebook.react.modules.core.DeviceEventManagerModule;
4. Sincewe'recreatinganativemoduleandstartingintents,weneedtoadd
someboilerplatecodeaswellasimplementingourshowSongsmethod,as
follows:
publicclassMediaManagerextendsReactContextBaseJavaModuleimplementsActivityEventListener{
privateMediaPlayermediaPlayer=null;
privateMediaMetadataRetrievermediaMetadataRetriever=null;
publicMediaManager(ReactApplicationContextreactApplicationContext){
super(reactApplicationContext);
reactApplicationContext.addActivityEventListener(this);
}
@Override
publicStringgetName(){
return"MediaManager";
}
@Override
publicvoidonCatalystInstanceDestroy(){
super.onCatalystInstanceDestroy();
mediaPlayer.stop();
mediaPlayer.release();
mediaPlayer=null;
}
@ReactMethod
publicvoidshowSongs(){
Activityactivity=getCurrentActivity();
Intentintent=newIntent(Intent.ACTION_PICK,MediaStore.Audio.Media.EXTERNAL_CONTENT_URI);
activity.startActivityForResult(intent,10);
}
@Override
publicvoidonActivityResult(Activityactivity,intrequestCode,intresultCode,Intentdata){
if(data!=null){
playSong(data.getData());
}
}
@Override
publicvoidonNewIntent(Intentintent){
}
privatevoidplaySong(Uriuri){
try{
if(mediaPlayer!=null){
mediaPlayer.stop();
mediaPlayer.reset();
}else{
mediaMetadataRetriever=newMediaMetadataRetriever();
mediaPlayer=newMediaPlayer();
mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
}
mediaPlayer.setDataSource(getReactApplicationContext(),uri);
mediaPlayer.prepare();
mediaPlayer.start();
mediaMetadataRetriever.setDataSource(getReactApplicationContext(),uri);
Stringartist=mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_ARTIST);
StringsongTitle=mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_TITLE);
WritableMapparams=Arguments.createMap();
params.putString("songPlaying",artist+"-"+songTitle);
getReactApplicationContext()
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
.emit("SongPlaying",params);
}catch(Exceptionex){
ex.printStackTrace();
}
}
}
5. ThecustommodulewillalsoneedtobeaddedtothegetPackagesarrayin
theMainApplication.javafile,asfollows:
protectedList<ReactPackage>getPackages(){
returnArrays.<ReactPackage>asList(
newMainReactPackage(),
newMediaPackage()
);
}
6. AscoveredintheExposingCustomAndroidModulesrecipeearlierinthis
chapter,wemustaddtherequisiteboilerplatetoMediaPackage.javafor
ourMediaManagercustommoduletobeexportedtotheReactNativelayer.
Refertothatrecipeforamorethoroughexplanation.Addtherequisite
boilerplateasfollows:
importcom.facebook.react.ReactPackage;
importcom.facebook.react.bridge.NativeModule;
importcom.facebook.react.bridge.ReactApplicationContext;
importcom.facebook.react.uimanager.ViewManager;
importjava.util.ArrayList;
importjava.util.Collections;
importjava.util.List;
publicclassMediaPackageimplementsReactPackage{
@Override
publicList<ViewManager>createViewManagers(ReactApplicationContextreactContext){
returnCollections.emptyList();
}
@Override
publicList<NativeModule>createNativeModules(ReactApplicationContextreactContext){
List<NativeModule>modules=newArrayList<>();
modules.add(newMediaManager(reactContext));
returnmodules;
}
}
7. TheJavaScriptlayerfortheAndroidappisidenticaltothatfoundinthe
previousiOSrecipe.Usestep7tostep10ofthisrecipetocompletethe
finalportionoftheapp.
Howitworks...
Thisrecipecoversplayingaudiofilesusingtheoperatingsystem'sbuilt-inaudio
support.IncontrasttoiOS,Androiddoesnothaveanoperatingsystem-levelUI
formediaplayback.Instead,theappmustdecidehowtheUIshouldbedesigned
andimplemented.
IntegrationwithNativeApplications
Inthischapter,wewillcoverthefollowingrecipes:
CombiningaReactNativeappandaNativeiOSapp
CommunicatingfromaniOSapptoReactNative
CommunicatingfromReactNativetoaniOSappcontainer
HandlingbeinginvokedbyanexternaliOSapp
EmbeddingaReactNativeappinsideaNativeAndroidapp
CommunicatingfromanAndroidapptoReactNative
CommunicatingfromReactNativetoanAndroidappcontainer
HandlingbeinginvokedbyanexternalAndroidapp
Introduction
ReactNativewasintroducedasasolutiontobuildnativeapplicationsusing
JavaScript,withthegoalofgrantingmoredeveloperstheabilitytobuildtruly
nativeapplicationsformultipleplatforms.AsaconsequenceofbuildingaReact
Nativeapplicationwithateam,itcanbecommonforJavaScriptdevelopersand
nativedeveloperstoworkcloselytogether.
OneoftheadvantagesofReactNative'sabilitytorendernativeUIviewsisthat
theycanbeeasilyembeddedinsideexistingnativeapps.Itisnotuncommonfor
companiestoalreadyhavesophisticatednativeappsthatarecriticaltotheirline
ofbusiness.Theremaybenoimmediateneedtorewritetheirentirecodebasein
ReactNativeiftheappisnotbroken.Insuchacase,ReactNativecanbe
leveragedbybothJavaScriptandnativedeveloperstowriteReactNativecode
thatcanbeintegratedintoanexistingapp.
ThischapterwillfocusexclusivelyonusingReactNativeinsideexistingnative
iOSandAndroidapplications.WewillcoverrenderingaReactNativeapp
withinanativeapp,howtocommunicatebetweentheReactNativeappandits
nativeparentapp,andhowourReactNativeappcanbeinvokedworkwithother
appsonauser'sdevice.
WhenworkingontheAndroidrecipes,itisrecommendedthatyouenabletheauto-import
settingsinAndroidStudiooruseAlt+Entertoperformaquickfixcodecompletionforthe
classimport.
CombiningaReactNativeappanda
NativeiOSapp
IntheeventthatyouworkforacompanyorhaveaclientthathasanactiveiOS
appoutintheworld,itmaynotbeadvantageoustorewriteitfromscratch,
especiallyifitiswell-built,usedfrequently,andpraisedbyitsusers.Ifyoujust
wanttobuildnewfunctionalityusingReactNative,theReactNativeappcanbe
embeddedandrenderedinsideanexistingnativeiOSapp.
ThisrecipewillwalkthroughcreatingablankiOSappandaddingittoaReact
Nativeappsothatthetwolayerscancommunicatewitheachother.Wewill
covertwowaysofrenderingtheReactNativeapp:embeddedinsidethe
applicationasanestedview,andanotherasafull-screenimplementation.The
stepsthatarediscussedinthisrecipeserveasabaselineforrenderingReact
Nativeapps,alongwithnativeiOSapps.
Gettingready
ThisrecipewillbereferencinganativeiOSapplicationnamedEmbeddedApp.We
willwalkthroughcreatingthesampleiOSapplicationinthissection.If
youalreadyhaveaniOSappyouintendonintegratingwithReactNative,you
canskipaheadtotherecipeinstructions.Youwill,however,needtobesurethat
youhavecocoapodsinstalled.ThislibraryisapackagemanagerforXcode
projects.ItcanbeinstalledviaHomebrewusingthefollowingcommand:
brewinstallcocoapods
Withcocoapodsinstalled,thenextstepiscreatinganewnativeiOSprojectin
Xcode.ThiscanbedonebyopeningXcodeandchoosingFile|New|Project.In
thewindowthatfollows,choosethedefaultSingleViewApplicationiOS
templatetogetstarted,andhitNext.
Intheoptionsscreenforthenewproject,besuretosettheProductNamefield
toEmbeddedApp:
Howtodoit...
1. We'llbeginbycreatinganewvanillaReactNativeappthatwillserveasthe
rootofourproject.Let'snamethenewprojectEmbedApp.Youcancreatethe
newReactNativeappwiththeCLIusingthefollowingcommand:
react-nativeinitEmbedApp
2. BycreatingthenewappwiththeCLI,theiosandandroidsubfolderswillbe
automaticallycreatedforus,holdingthenativecodeforeachplatform.
Let'smovethenativeappwecreatedintheGettingreadysectiontotheios
foldersothatitlivesat/EmbedApp/ios/EmbeddedApp.
3. Nowthatwehavethebasicstructureweneedfortheapp,we'llneedtoadd
aPodfile.Thisisafile,similartopackage.jsoninwebdevelopment,that
keepstrackofallofthecocoapoddependencies(calledpods)thatareused
inaproject.ThePodfileshouldalwaysliveintherootofthevanillaiOS
project,whichinourcaseis/EmbedApp/ios/EmbeddedApp.InaTerminal,cdinto
thisdirectoryandrunthepodinitcommand.ThisgeneratesabasePodfile
foryou.
4. Next,openthePodfileinyourfavoriteIDE.We'llbeaddingthepodsthat
areneededfortheapptothisfile.Thefollowingisthecontentsofthefinal
Podfile,withthenewlyaddedReactNativedependenciesinbold:
target'EmbeddedApp'do
#Uncommentthenextlineifyou'reusingSwiftorwouldliketousedynamicframeworks
#use_frameworks!
#PodsforEmbeddedApp
target'EmbeddedAppTests'do
inherit!:search_paths
#Podsfortesting
end
target'EmbeddedAppUITests'do
inherit!:search_paths
#Podsfortesting
end
#Podsthatwillbeusedintheapp
pod'React',:path=>'../../node_modules/react-native',:subspecs=>[
'Core',
'CxxBridge',#IncludethisforRN>=0.47
'DevSupport',#IncludethistoenableIn-AppDevmenuifRN>=0.43
'RCTText',
'RCTNetwork',
'RCTWebSocket',#Neededfordebugging
'RCTAnimation',#NeededforFlatListandanimationsrunningonnativeUIthread
#Addanyothersubspecsyouwanttouseinyourproject
]
#ExplicitlyincludeYogaifyouareusingRN>=0.42.0
pod'yoga',:path=>'../../node_modules/react-native/ReactCommon/yoga'
#Thirdpartydepspodspeclink
pod'DoubleConversion',:podspec=>'../../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec'
pod'glog',:podspec=>'../../node_modules/react-native/third-party-podspecs/glog.podspec'
pod'Folly',:podspec=>'../../node_modules/react-native/third-party-podspecs/Folly.podspec'
end
NoticehoweachofthepathslistedintheReactNativedependenciesthatwe'readdingpoint
tothe/node_modulesfolderoftheReactNativeproject.Ifyournativeproject(inour
case,EmbeddedApp)wasatadifferentlocation,thesereferencesto/node_moduleswouldhavetobe
updatedaccordingly.
5. WiththePodfileinplace,installingthepodsthemselvesisaseasyas
runningthepodinstallcommandfromtheTerminalinthesamedirectory
wecreatedthePodfile.
6. Next,let'sreturntotheReactNativeappattherootdirectoryofthe
project,/EmbedApp.We'llstartbyremovingthegeneratedcodeinindex.js,and
replacingitwithourownsimpleReactNativeapp.Thiswillbeavery
simpleappthatjustrendersthetextHelloinReactNativesothatitcanbe
distinguishedfromthenativelayerinlatersteps:
importReact,{Component}from'react';
import{
AppRegistry,
StyleSheet,
View,
Text
}from'react-native';
classEmbedAppextendsComponent{
render(){
return(
<Viewstyle={styles.container}>
<Text>HelloinReactNative</Text>
</View>
);
}
}
conststyles=StyleSheet.create({
container:{
flex:1,
justifyContent:'center',
alignItems:'center',
backgroundColor:'#F5FCFF',
}
});
AppRegistry.registerComponent('EmbedApp',()=>EmbedApp);
7. NowthatwehaveaReactNativeapp,wecanmovetothenativecode.
Whenweinitializedcocoapodsinstep3,italsogeneratedanew.xcworkspace
file.BesuretoclosetheEmbeddedAppprojectinXcode,thenre-openitin
XcodeusingtheEmbeddedApp.xcworkspacefile.
8. InXcode,let'sopenMain.storyboard:
9. Inthestoryboard,we'llneedtoaddtwobuttons:onelabeledOpenReact
NativeAppandonelabeledOpenReactNativeApp(Embedded).We'll
alsoneedanewcontainerviewbelowthetwobuttons.Theresulting
storyboardshouldlooksomethinglikethis:
10. Next,we'llneedanewanewCocoaTouchClass.Thiscanbecreatedfrom
themenusbychoosingFile|New|File.We'llnamethe
classEmbeddedViewControllerandassignitasubclassofUIViewController:
11. Let'sreturntoMain.storyboard.Inthenewscenethat'screatedbyaddingthe
classinthepreviousstep(secondViewControllerScene),selecttheView
Controllerchild.MakesurethattheIdentityinspectorisopenintheright-
handpanel:
WiththeViewControllerselected,changetheClassvaluetoournewly
createdclass,EmbeddedViewController:
12. Next,inthetopViewControllerScene,selecttheEmbedsegueobject:
13. Withthesegueselected,selecttheAttributesinspectorfromtheright-hand
panel,andupdatetheIdentifierfieldtotheembedvalue.Wewillusethis
identifiertoembedtheReactNativelayerwithinthenativeapp:
14. We'rereadytobuildouttheViewControllerimplementation.Open
theViewController.mfile.We'llstartwiththeimports:
#import"ViewController.h"
#import"EmbeddedViewController.h"
#import<React/RCTRootView.h>
15. Justbeneaththeimports,wecanaddaninterfacedefinitiontopointtothe
EmbeddedViewControllerwecreatedinstep10:
@interfaceViewController(){
EmbeddedViewController*embeddedViewController;
}
@end
16. Followingisthe@interface,we'lladdthemethodsweneedtothe
@implementation.Thefirstmethod,openRNAppButtonPressed,willbewiredtothe
firstbuttonwecreatedinthestoryboard,labeledOpenReactNativeApp.
Likewise,theopenRNAppEmbeddedButtonPressedmethodwillbewiredtothe
secondbutton,OpenReactNativeApp(Embedded).
You'lllikelynoticethatthemethodsarealmostidentical,withthesecond
methodreferencingembeddedViewController,thesameEmbeddedViewControllerclass
wecreatedinstep10([embeddedViewControllersetView:rootView];).Both
methodsdefinejsCodeLocationwiththevalue
ofhttp://localhost:8081/index.bundle?platform=ios,whichistheURLthatthe
ReactNativeappwillbeservedfrom.Also,takenotethatthemoduleName
propertyinbothmethodsissettoEmbedApp,whichisthenamethattheReact
Nativeappisexportedas,whichwedefinedinstep6:
@implementationViewController
-(void)viewDidLoad{
[superviewDidLoad];
//Doanyadditionalsetupafterloadingtheview,typicallyfromanib.
}
-(void)didReceiveMemoryWarning{
[superdidReceiveMemoryWarning];
//Disposeofanyresourcesthatcanberecreated.
}
-(IBAction)openRNAppButtonPressed:(id)sender{
NSURL*jsCodeLocation=[NSURL
URLWithString:@"http://localhost:8081/index.bundle?platform=ios"];
RCTRootView*rootView=
[[RCTRootViewalloc]initWithBundleURL:jsCodeLocation
moduleName:@"EmbedApp"
initialProperties:nil
launchOptions:nil];

UIViewController*vc=[[UIViewControlleralloc]init];
vc.view=rootView;
[selfpresentViewController:vcanimated:YEScompletion:nil];
}
-(IBAction)openRNAppEmbeddedButtonPressed:(id)sender{
NSURL*jsCodeLocation=[NSURL
URLWithString:@"http://localhost:8081/index.bundle?platform=ios"];
RCTRootView*rootView=
[[RCTRootViewalloc]initWithBundleURL:jsCodeLocation
moduleName:@"EmbedApp"
initialProperties:nil
launchOptions:nil];

[embeddedViewControllersetView:rootView];
}
//Definedinnextstep
@end
17. We'llalsoneedtodefinetheprepareForSeguemethod.Here,youcan
seesegue.identifierisEqualToString:@"embed",whichreferstotheembed
identifierwegavethesegueinstep13:
//Definedinprevioussteps
-(void)prepareForSegue:(UIStoryboardSegue*)seguesender:(id)sender{
if([segue.identifierisEqualToString:@"embed"]){
embeddedViewController=segue.destinationViewController;
}
}
@end
18. WithourimplementationofViewControllerinplace,wenowweneedtowire
upourbuttonactionstothebuttonsthemselves.Let'sreturn
toMain.storyboard.Ctrl+clickonthefirstbuttontogetamenuofactions
thatareassignabletothebutton,selecttheTouchUpInsideactionby
clickinganddraggingfromTouchUpInsidebacktothestoryboard,and
mapthebuttontotheopenRNAppButtonPressedmethodwedefinedinstep15.
Repeatthesestepsforthesecondbutton,linkingitinsteadtothe
openRNAppEmbeddedButtonPressedmethod:
19. FortheReactNativelayertobeabletocommunicatewiththenativelayer,
wealsoneedtoaddasecurityexception,whichwillallowourcodeto
communicatewithlocalhost.Right-clickontheInfo.plistfileandselect
OpenAs|SourceCode.Withinthebase<dict>tag,addthefollowingentry:
<key>NSAppTransportSecurity</key>
<dict>
<key>NSExceptionDomains</key>
<dict>
<key>localhost</key>
<dict>
<key>NSTemporaryExceptionAllowsInsecureHTTPLoads</key>
<true/>
</dict>
</dict>
</dict>
20. Ourappiscomplete!Fromthe/EmbedApprootdirectory,startuptheReact
NativeappusingtheCLIwiththefollowingcommand:
react-nativestart
21. WiththeReactNativeapprunning,let'salsoruntheNativeappfrom
Xcode.Now,pressingtheOpenReactNativeAppbuttonshouldopenthe
ReactNativeappwecreatedinstep6infullscreen,andthesameReact
Nativeappshouldopenwithinthecontainerviewwecreatedinstep9
whenpressingtheOpenReactNativeApp(Embedded)button.
Howitworks...
Inthisrecipe,wecoveredrenderingaReactNativeappwithinanativeiOSapp
viatwodifferentmethods.Thefirstmethodreplacestheapplication'smain
UIViewControllerinstancewiththeReactNativeapp,referredtointhenativecode
asRCTRootView.ThiswasaccomplishedintheopenRNAppButtonPressedmethod.The
secondandslightlymoreinvolvedmethodinvolvesrenderingtheReactNative
appinlinewiththenativeapp.Thiswasaccomplishbycreatingacontainerview
thatlinkstoadifferentUIViewControllerinstance.Inthiscase,wereplaced
thecontentsofembedViewControllerwithourRCTRootViewinstance.Thisiswhat
happenswhentheopenRNAppEmbeddedButtonPressedmethodisfired.
Seealso
ForabetterunderstandingoftherolecocoapodsplaysinXcode/ReactNative
development,IrecommendGoogle'sRoute85Showepisodecoveringthe
subjectonYouTube.Thevideocanbefoundathttps://www.youtube.com/watch?v=iEAjv
NRdZa0.
CommunicatingfromaniOSappto
ReactNative
Inthepreviousrecipe,welearnedhowtorenderaReactNativeappaspartofa
largernativeiOSapp.Unlessyou'rebuildingaglorifiedappcontainerorportal,
you'lllikelyneedtocommunicatebetweenthenativelayerandtheReactNative
layer.Thiswillbethesubjectmatterofthenexttworecipes,onerecipeforeach
directionofcommunication.
Inthisrecipe,wewillcovercommunicatingfromthenativelayertotheReact
Nativelayer,sendingdatafromtheparentiOSapptoourembeddedReact
Nativeapp,byusingaUITextFieldintheiOSappthatsendsitsdatatotheReact
Nativeapp.
Gettingready
SincethisreciperequiresanativeappwithanestedReactNativeappwithinit,
we'llbebeginningattheendofthepreviousrecipe,effectivelypickingupwhere
weleftoff.Thiswillhelpyouunderstandhowbasiccross-layercommunication
workssothatyoucanusethesameprinciplesinyourownnativeapp,which
mayalreadyexistandhavecomplexfeatures.Therefore,theeasiestwayto
followalongwiththisrecipeistousetheendpointofthepreviousrecipeasa
startingplace.
Howtodoit...
1. Let'sstartbyupdatingtheViewController.mimplementationfileinthenative
layer.BesuretoopentheprojectinXcodeviathe.xcworkspacefileinthe
EmbeddedApp,whichweplacedinthe/ios/EmbeddAppdirectoryoftheprojectin
thepreviousrecipe.We'llstartwiththeimports:
#import"ViewController.h"
#import"EmbeddedViewController.h"
#import<React/RCTRootView.h>
#import<React/RCTBridge.h>
#import<React/RCTEventDispatcher.h>
2. ThenextstepistoaddareferencetotheReactNativebridgevia
theViewControllerinterface,effectivelylinkingthenativecontrollerwiththe
ReactNativecode:
@interfaceViewController()<RCTBridgeDelegate>{
EmbeddedViewController*embeddedViewController;
RCTBridge*_bridge;
BOOLisRNRunning;
}
3. Wewillalsoneedan@propertyreferenceofuserNameFieldthatwewilluseina
latersteptowiretotheUITextField:
@property(weak,nonatomic)IBOutletUITextField*userNameField;
@end
4. Directlybelowthisreference,we'llbegindefiningtheclassmethods.We'll
beginwiththesourceURLForBridgemethod,whichdefineswheretheReact
Nativeappwillbeservedfrom.Inourcase,theappURLshould
behttp://localhost:8081/index.bundle?platform=ios,whichpointsattheindex.js
fileoftheReactNativeapponceitisrunwiththereact-nativestart
command:
-(NSURL*)sourceURLForBridge:(RCTBridge*)bridge{
NSURL*jsCodeLocation=[NSURL
URLWithString:@"http://localhost:8081/index.bundle?platform=ios"];
returnjsCodeLocation;
}
5. We'llleavetheviewDidLoadanddidReveiveMemoryWarningmethodsasis:
-(void)viewDidLoad{
[superviewDidLoad];
}
-(void)didReceiveMemoryWarning{
[superdidReceiveMemoryWarning];
//Disposeofanyresourcesthatcanberecreated.
}
6. Next,we'llneedtoupdatetheopenRNAppEmbeddedButtonPressedmethod.Notice
howthemoduleNamepropertyissettoFromNativeToRN.Thisisareferencetothe
namethatwegivetheReactNativeappwhenitisexported,whichwe'll
defineinalaterstep.Thistime,wearealsodefiningapropertyofuserName
forpassingdatatotheReactNativelayer:
-(IBAction)openRNAppEmbeddedButtonPressed:(id)sender{
NSString*userName=_userNameField.text;
NSDictionary*props=@{@"userName":userName};

if(_bridge==nil){
_bridge=[[RCTBridgealloc]initWithDelegate:self
launchOptions:nil];
}

RCTRootView*rootView=
[[RCTRootViewalloc]initWithBridge:_bridge
moduleName:@"FromNativeToRN"
initialProperties:props];

isRNRunning=true;
[embeddedViewControllersetView:rootView];
}
7. We'llalsoneedanonUserNameChangedmethod.Thisisthemethodthatwilldo
theactualsendingofdataacrossthebridgetotheReactNativelayer.The
eventnamewe'redefininghereisUserNameChanged,whichwe'llreferencein
theReactNativelayerinalaterstep.Thiswillalsopassalongthetextthat's
currentlyinthetextinput,whichwillbenameduserNameField:
-(IBAction)onUserNameChanged:(id)sender{
if(isRNRunning==YES&&_userNameField.text.length>3){
[_bridge.eventDispatchersendAppEventWithName:@"UserNameChanged"body:@{@"userName":_userNameField.text}];
}
}
8. We'llalsoneedprepareForSegueforconfiguringembeddedViewControllerjust
beforeitisdisplayed:
-(void)prepareForSegue:(UIStoryboardSegue*)seguesender:(id)sender{
if([segue.identifierisEqualToString:@"embed"]){
embeddedViewController=segue.destinationViewController;
}
}
@end
9. BackintheMain.storyboard,let'saddthatTextField,alongwithaLabelthat
defineswhattheinputisfor.YoucanalsonametheinputUserName
FieldsothateverythingiseasiertorecognizeintheViewControllerScene:
10. Next,we'llneedtowireaneventforwhenthetextchangesintheUser
NameFieldtextinput,andareferencingoutletsothattheViewController
knowshowtoreferenceit.ThesecanbothbedoneviatheConnections
Inspector,whichisaccessibleviathelastbuttonalongthetopoftheright-
handsidepanel(theiconisarightpointingarrowinacircle).Withthetext
inputselected,clickanddragfromEditingChangedtotheViewController
(representedviathemainstoryboard),andchoosetheonUserNameChange
methodwedefinedinstep7.Then,createthefollowingwiringsby
draggingtheitemtotheViewController.Similarly,addanewReferencing
OutletbyclickinganddraggingfromtheNewReferencingOutletbackto
theViewController,thistimechoosingtheuserNameFieldvaluewe
targetedinstep7.YourConnectionsInspectorsettingsshouldnowlooklike
this:
11. We'venowcompletedthestepsneededinthenativeapp.Let'smoveonto
theReactNativelayer.Backintheindex.jsfile,we'llstartwithimports.
Noticehowwe'renowincludingtheNativeAppEventEmitter.
12. Putthefollowingfunctionsinsidetheclassdefinition:
importReact,{Component}from'react';
import{
AppRegistry,
StyleSheet,
View,
Text,
NativeAppEventEmitter
}from'react-native';
13. We'llnametheappFromNativeToRNtomatchthemodulenamewedefinedin
thenativelayerinstep6,usingAppRegistry.registerComponenttoregisterthe
appwiththesamename.We'llalsoleavethebasicstylesinplace:
classFromNativeToRNextendsComponent{
//Definedinfollowingsteps
}
conststyles=StyleSheet.create({
container:{
flex:1,
justifyContent:'center',
alignItems:'center',
backgroundColor:'#F5FCFF',
}
});
AppRegistry.registerComponent('FromNativeToRN',()=>FromNativeToRN);
14. We'llsetaninitialstateobjectwithauserNamestringpropertytostoringand
displayingthetextthat'sreceivedfromthenativelayer:
classFromNativeToRNextendsComponent{
state={
userName:''
}
//Definedinfollowingsteps
}
15. TheuserNamevaluepassedintotheReactNativelayerwillbereceivedasa
property.Whenthecomponentmounts,wewanttodotwothings:set
theuserNamestatepropertyifit'salreadydefinedbythenativelayer,andwire
aneventlistenertoupdateuserNamewhenthetextfieldinthenativelayeris
updated.Recallinstep7thatwedefinedtheevent'snameto
beUserNameChanged,sothat'stheeventwe'lllistenfor.Whentheeventis
received,weupdatethestate.userNametothetextthat'spassedalongwiththe
event:
componentWillMount(){
this.setState({
userName:this.props.userName
});
NativeAppEventEmitter.addListener('UserNameChanged',(body)=>{
this.setState({userName:body.userName});
});
}
16. Finally,wecanaddtherenderfunction,whichsimplyrendersthevalue
storedinstate.userName:
render(){
return(
<Viewstyle={styles.container}>
<Text>Hello{this.state.userName}</Text>
</View>
);
}
17. It'stimetorunourapp!First,intherootoftheproject,wecanstartupthe
ReactNativeappwiththeReactNativeCLIwiththefollowingcommand:
react-nativestart
WefollowthisbyrunningthenativeappinthesimulatorviaXcode:
CommunicatingfromReactNativeto
aniOSappcontainer
Thelastrecipecoveredcommunicationbetweenlayersinthedirectionofnative
toReactnative.Inthisrecipe,wewillcovercommunicatingintheopposite
direction:fromReactNativetonative.Thistime,wewillrenderauserinput
elementinsideourReactNativeappandsetupaone-waybindingfromReact
NativetoaUIcomponentrenderedinthenativeapp.
Gettingready
Justlikethelastrecipe,thisrecipedependsonthefinalproductofthefirstapp
inthischapter,intheCombiningaReactNativeappandaNativeiOS
apprecipe.Tofollowalong,besureyou'vefinishedthatrecipe.
Howtodoit...
1. Let'sbegininthenativelayer.OpentheEmbeddedAppnativeappinXcodevia
the.xcworkspacefile.We'llfirstaddimportstoViewController.m:
#import"ViewController.h"
#import"EmbeddedViewController.h"
#import<React/RCTRootView.h>
#import<React/RCTBridge.h>
#import<React/RCTEventDispatcher.h>
2. Aswedidinthelastrecipe,weneedtoaddareferencetotheReactNative
bridgeviatheViewControllerinterface,providingabridgebetweenthenative
controllerandtheReactNativecode:
@interfaceViewController()<RCTBridgeDelegate>{
EmbeddedViewController*embeddedViewController;
RCTBridge*_bridge;
BOOLisRNRunning;
}
3. Wewillalsoneeda@propertyreferenceofuserNameFieldthatwewilluseina
latersteptowiretotheUITextField:
@property(weak,nonatomic)IBOutletUITextField*userNameField;
@end
4. Let'smoveontodefiningthe@implementation.Again,wemustprovidethe
sourceoftheReactNativeapp,whichwillbeservedfromlocalhost:
@implementationViewController
-(NSURL*)sourceURLForBridge:(RCTBridge*)bridge{
NSURL*jsCodeLocation=[NSURL
URLWithString:@"http://localhost:8081/index.bundle?platform=ios"];
returnjsCodeLocation;
}
5. UsingtheviewDidLoadmethod,wecanalsoconnectthecontrollertothe
methodthatopenstheReactNativeappinourcontainerview
(openRNAppEmbeddedButtonPressed).We'llleavethedidReveiveMemoryWarningmethod
asis:
-(void)viewDidLoad{
[superviewDidLoad];
[selfopenRNAppEmbeddedButtonPressed:nil];
}
-(void)didReceiveMemoryWarning{
[superdidReceiveMemoryWarning];
//Disposeofanyresourcesthatcanberecreated.
}
6. Likethelastrecipe,we'llneedtoupdatetheopenRNAppEmbeddedButtonPressed
method.Thistime,themoduleNamepropertyissettoFromRNToNativetoreflectthe
namethatwewillgivetheReactNativeappwhenitisexported,asdefined
inalaterstep.WealsodefineapropertyofuserNameforpassingdatatothe
ReactNativelayer:
-(IBAction)openRNAppEmbeddedButtonPressed:(id)sender{
if(_bridge==nil){
_bridge=[[RCTBridgealloc]initWithDelegate:selflaunchOptions:nil];
}

RCTRootView*rootView=
[[RCTRootViewalloc]initWithBridge:_bridge
moduleName:@"FromRNToNative"
initialProperties:nil];

isRNRunning=true;
[embeddedViewControllersetView:rootView];
}
7. Thelasttwomethodswe'llneedinthisfileareprepareForSeguefor
configuringtheembeddedViewControllerjustbeforeitisdisplayed,and
anupdateUserNameFieldmethodthatwillbefiredwhenourtextinputinthe
nativelayerisupdatedwithnewtextfromtheuser:
-(void)prepareForSegue:(UIStoryboardSegue*)seguesender:(id)sender{
if([segue.identifierisEqualToString:@"embed"]){
embeddedViewController=segue.destinationViewController;
}
}
-(void)updateUserNameField:(NSString*)userName{
[_userNameFieldsetText:userName];
}
@end
8. Unlikethepreviousrecipe,we'llneedtoalsoupdate
theViewControllerheaderfile(ViewController.h).Themethodreferenced
here,updateUserNameField,willbeusedwhenwedefinetheViewController
implementation:
#import<UIKit/UIKit.h>
@interfaceViewController:UIViewController
-(void)updateUserNameField:(NSString*)userName;
@end
9. Next,we'regoingtoneedtocreateanewUserNameManagernativemodule.
First,createaCocoaTouchclassnamedUserNameManager.Oncecreated,let's
opentheimplementationfile(UserNameManger.m)andaddourimports:
#import"UserNameManager.h"
#import"AppDelegate.h"
#import"ViewController.h"
#import<React/RCTBridgeModule.h>
Foramorein-depthlookatcreatingnativemodules,refertotheExposingCustom
iOSModulesrecipeinChapter11,AddingNativeFunctionality.
10. Then,we'lldefinetheclassimplementation.Themaintakeawayhereis
thesetUserNamemethod,whichisthemethodthatwe'reexportingfromthe
nativelayerforuseintheReactNativeapp.We'llusethismethodinthe
ReactNativeapptoupdatethevalueinthenativeTextField.However,
sinceweareupdatinganativeUIcomponent,theoperationmustbe
performedonthemainthread.ThisisthepurposeofthemethodQueue
function,whichinstructsthemoduletoexecuteonthemainthread:
@implementationUserNameManager
RCT_EXPORT_MODULE();
-(dispatch_queue_t)methodQueue
{
returndispatch_get_main_queue();
}
RCT_EXPORT_METHOD(setUserName:(NSString*)userName){
AppDelegate*delegate=(AppDelegate*)[[UIApplicationsharedApplication]delegate];
ViewController*controller=(ViewController*)delegate.window.rootViewController;

[controllerupdateUserNameField:userName];
}
@end
11. We'llalsoneedtoupdatetheUserNameMangager.hheaderfiletousetheReact
Nativebridgemodule:
#import<Foundation/Foundation.h>
#import<React/RCTBridgeModule.h>
@interfaceUserNameManager:NSObject<RCTBridgeModule>
@end
12. Likethelastrecipe,we'llneedtoaddaTextFieldandLabelfortheUser
Nameinput:
13. We'llalsoneedtoaddaReferencingOutletfromtheTextFieldwecreated
inthelastsettoouruserNameFieldproperty:
IfyouneedmoreinformationonhowtocreateaReferencingOutlet,viewstep10ofthe
previousrecipe.
14. We'refinishedwiththenativeportionofthisproject,solet'sturntoour
ReactNativecode.Let'sopentheindex.jsfileattherootoftheproject.
We'llstartwithourimports:
importReact,{Component}from'react';
import{
AppRegistry,
StyleSheet,
View,
Text,
TextInput,
NativeModules
}from'react-native';
15. Let'sdefinetheappwiththenameFromRNToNativetolineupwiththemoduleName
wedeclaredinthenativecodeinstep6,andregisterthecomponentwith
thesamename.ThestateobjectonlyneedsauserNamestringpropertyfor
holdthevaluethat'ssavedtotheTextInputcomponent,whichwe'lladdinthe
component'srenderfunction:
classFromRNToNativeextendsComponent{
state={
userName:''
}
//Definedonnextstep
}
AppRegistry.registerComponent('FromRNToNative',()=>FromRNToNative);
16. Theapp'srenderfunctionusesaTextInputcomponenttotakeinputfromthe
user,whichitwillthensendtothenativeappviatheReactNativebridge.It
doesthisbycallingtheonUserNameChangemethodwhenthevalueofthe
TextInputchanges:
render(){
return(
<Viewstyle={styles.container}>
<Text>EnterUserName</Text>
<TextInput
style={styles.userNameField}
onChangeText={this.onUserNameChange}
value={this.state.userName}
/>
</View>
);
}
17. ThelastthingweneedtodoisdefinetheonUserNameChangemethodthat'sused
bytheonChangeTextpropertyoftheTextInputcomponentwedefinedinthe
previousstep.Thismethodupdatesstate.userNametothevalueinthetext
input,andalsosendsthevaluealongtothenativecodebyusingthe
NativeModulescomponentinReactNative.NativeModuleshastheUserNameManager
classwedefinedasaCocoaTouchclassinthenativelayerinstep9.We
callthesetUserNamemethodthatwedefinedontheclassinstep10topassthe
valuealongtothenativelayer,whereitwillbedisplayedintheText
Fieldwecreatedinstep12:
onUserNameChange=(userName)=>{
this.setState({userName});
NativeModules.UserNameManager.setUserName(userName);
}
18. Theappisdone!ReturntotherootoftheprojecttostartuptheReact
Nativeappwiththefollowingcommand:
react-nativestart
Then,withtheReactNativeappstarted,runthenativeEmbeddedAppproject
fromXcode.Now,theinputintheReactNativeappshouldcommunicate
itsvaluetotheinputintheparentnativeapp:
Howitworks...
TocommunicatefromourReactNativeapptotheparentnativeapp,wecreated
anativemodulenamedUserNameManagerwithasetUserNamemethod,whichwe
exportedfromthenativelayer,andusedintheReactNativeapp,inits
onUserNameChangemethod.Thisistherecommendedwayofcommunicatingfrom
ReactNativetonative.
Handlingbeinginvokedbyan
externaliOSapp
Itisalsoacommonbehaviorfornativeappstocommunicatebetweenone
anothervialinking,andareusuallypromptedtotheuserwiththephraseOpen
in...,alongwiththenameofanappthatcanbetterhandleanaction.Thisisdone
byusingaprotocolthatisspecifictoyourapp.Justlikeanywebsitelinkhasa
protocolofeitherhttp://orhttps://,wecanalsocreateacustomprotocolthat
willallowanyotherapptoopenandsenddatatoourapp.
Inthisrecipe,wewillbecreatingacustomprotocolcalledinvoked://.Byusing
theinvoked://protocol,anyotherappcanuseittorunourappandpassdatatoit.
Gettingready
Forthisrecipe,we'llbestartingfromanewvanillaReactNativeapp.Let'sname
itInvokeFromNative.
Howtodoit...
1. Let'sstartbyopeningthenativelayerofthenewprojectinXcode.Thefirst
thingweneedtodoisadjusttheproject'sBuildSettings.Thiscanbedone
byselectingtherootprojectintheleftpanel,thenchoosingtheBuild
Settingstabalongthetopofthemiddlepanel:
2. We'llneedtoaddanewentrytotheHeaderSearchPathsfield:
FortheprojecttoknowthelocationoftheReactNativeJavaScript,it
needsthe$(SRCROOT)/../node_modules/react-native/Librariesvalue.Let'saddit
asarecursiveentry:
3. Wealsoneedtoregisterourcustomprotocol,whichwillbeusedbyother
apps.OpentheInfo.plistfileassourcecode(right-clickthenOpenAs
|SourceCode).Let'saddanentrytothefilethatwillregisterour
applicationundertheinvoked://protocol:
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLSchemes</key>
<array>
<string>invoked</string>
</array>
</dict>
</array>
4. Next,weneedtoaddtheRCTLinkingManagertotheAppDelegateimplementation,
whichlivesinAppDelegate.m,andwireittoourapp:
#import"AppDelegate.h"
#import<React/RCTBundleURLProvider.h>
#import<React/RCTRootView.h>
#import<React/RCTLinkingManager.h>
@implementationAppDelegate
//TherestoftheAppDelegateimplementation
-(BOOL)application:(UIApplication*)application
openURL:(NSURL*)url
options:(NSDictionary<UIApplicationOpenURLOptionsKey,id>*)options
{
return[RCTLinkingManagerapplication:applicationopenURL:urloptions:options];
}
@end
5. Now,let'smoveontotheReactNativelayer.Insideindex.js,we'lladdour
imports,whichincludestheLinkingcomponent:
importReact,{Component}from'react';
import{
AppRegistry,
StyleSheet,
Text,
View,
Linking
}from'react-native';
6. Next,we'llcreatetheclassdefinitionandregisterthecomponent
asInvokeFromNative.We'llalsodefineaninitialstateobjectwithastatusstring
propertysettothevalue'AppRunning':
classInvokeFromNativeextendsComponent{
state={
status:'AppRunning'
}
//Definedonfollowingsteps
}
AppRegistry.registerComponent('InvokeFromNative',()=>InvokeFromNative);
7. Now,we'llusethemountandunmountlifecyclehookstoadd/removethe
eventlistenerfortheinvoked://protocol.Whentheeventisheard,the
onAppInvokedmethod,whichisdefinedinthenextstep,willbefired:
componentWillMount(){
Linking.addEventListener('url',this.onAppInvoked);
}
componentWillUnmount(){
Linking.removeEventListener('url',this.onAppInvoked);
}
8. TheonAppInvokedfunctionsimplytakestheeventfromtheeventlistenerand
updatesstate.statustoreflectthatinvocationhashappened,displayingthe
protocolviaevent.url:
onAppInvoked=(event)=>{
this.setState({
status:`AppInvokedby${event.url}`
});
}
9. Therendermethod'sonlyrealpurposeinthisrecipeistorenderthestatus
propertyonstate:
render(){
return(
<Viewstyle={styles.container}>
<Textstyle={styles.instructions}>
AppStatus:
</Text>
<Textstyle={styles.welcome}>
{this.state.status}
</Text>
</View>
);
}
10. We'llalsoaddafewbasicstylestocenterandsizethetext:
conststyles=StyleSheet.create({
container:{
flex:1,
justifyContent:'center',
alignItems:'center',
backgroundColor:'#F5FCFF',
},
welcome:{
fontSize:20,
textAlign:'center',
margin:10,
},
instructions:{
textAlign:'center',
color:'#333333',
marginBottom:5,
},
});
11. Ourappisfinished.Onceyou'vestartedrunningtheapp,youshouldsee
somethinglikethis:
12. Withtheapprunning,wecansimulatetheactionofanotherappopening
ourReactNativeappusingtheinvoked://protocol.Thiscanbedonewith
thefollowingTerminalcommand:
xcrunsimctlopenurlbootedinvoked://
Onceinvoked,theappshouldupdatetoreflecttheinvocation:
Howitworks...
Inthisrecipe,wecoveredhowtoregisteracustomprotocol(orURLschema)
forallowingourapptobeinvokedbyotherapps.Theaimofthisrecipewasto
keepourexampleassimpleaspossible,sowedidnotbuildoutthehandling
datawepassedtoanappviathelinkingmechanism.However,itisentirely
possibletodosoiftheneedsofyourapprequireit.Foradeeperdiveonthe
Linkingcomponent,checkouttheofficialdocumentsathttps://facebook.github.io/re
act-native/docs/linking.
EmbeddingaReactNativeappinside
aNativeAndroidapp
SincetheAndroidplatformstillholdsthemajoritystakeinthesmartphone
marketspace,it'slikelythatyou'llwanttobuildtheappforbothAndroidaswell
asiOS.AlargeadvantageofReactNativedevelopmentismakingthisprocess
easier.ButwhathappenswhenyouwanttowriteanewfeatureusingReact
NativeforaworkingAndroidappthat'salreadybeenpublished?
Fortunately,ReactNativemakesthispossibleaswell.
ThisrecipewillcovertheprocessofembeddingaReactNativeappinsidean
existingAndroidappbydisplayingtheReactNativeappinsideacontainerview.
Thestepshereareusedasabaselinefortherecipesthatfollow,whichinvolve
communicationwithaReactNativeapp.
Gettingready
Inthissection,wewillcreateasampleAndroidapplicationusingAndroid
StudiocalledEmbedApp.IfyouhaveabaseAndroidapplicationyouwouldliketo
workwith,youcanskipthesestepsandproceedtotheactualimplementation:
1. OpenAndroidStudioandcreateanewproject(File|NewProject)
2. SettheapplicationnametoEmbeddedAppandfilloutyourcompanydomain.
PressNext
3. LeaveEmptyActivityselectedasthedefaultandpressNext
4. LeavetheActivitypropertiesastheyarebydefaultandpressFinish
Howtodoit...
1. Atthispoint,ourapphasnoreferencestoReactNative,sowe'llstartby
installingit.Intheapp'srootfolder,intheTerminal,installReactNative
fromthecommandlineusingyarn:
yarnaddreact-native
Alternatively,youcanusenpm:
npminstallreact-native--save
2. We'llalsoneedaNode.jsscriptforstartingtheReactNativeapp.Let's
openpackage.jsonandaddthefollowingpropertyasamemberof
thescriptsobject:
"start":"nodenode_modules/react-native/local-cli/cli.jsstart"
3. WeonlyneedaverysimpleReactNativeappforthisrecipe.Let'screate
anindex.android.jsfilewiththefollowingboilerplateapp:
importReact,{Component}from'react';
import{AppRegistry,StyleSheet,View,Text}from'react-native';
exportdefaultclassEmbedAppextendsComponent{
render(){
return(<Viewstyle={styles.container}>
<Text>HelloinReactNative</Text>
</View>);
}
}
conststyles=StyleSheet.create({
container:{
flex:1,
justifyContent:'center',
alignItems:'center',backgroundColor:'#F5FCFF'
}
});
AppRegistry.registerComponent('EmbedApp',()=>EmbedApp);
4. Let'sreturntoAndroidStudioandopenthebuild.gradlefile(from
theappmodule)beforeaddingthefollowingtothedependencies:
dependencies{
implementationfileTree(dir:"libs",include:["*.jar"])
implementation"com.android.support:appcompat-v7:27.1.1"
implementation"com.facebook.react:react-native:+"//Fromnode_modules
}
5. We'llalsoneedareferencetothelocalReactNativemavendirectory.Open
theotherbuild.gradleandaddthefollowinglinetotheallprojects.repositories
object:
allprojects{
repositories{
mavenLocal()
maven{
url"$rootDir/../node_modules/react-native/android"
}
google()
jcenter()
}
}
6. Next,let'supdatetheapp'spermissionstousetheinternet,andthesystem
alterwindow.We'llopenAndroidManifest.xmlandaddthefollowing
permissionstothe<manifest>node:
<?xmlversion="1.0"encoding="utf-8"?>
<manifestxmlns:android="http://schemas.android.com/apk/res/android"
package="com.warlyware.embeddedapp">
<uses-permissionandroid:name="android.permission.INTERNET"/>
<uses-permissionandroid:name="android.permission.SYSTEM_ALERT_WINDOW"/>
<application
android:name=".EmbedApp"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activityandroid:name=".MainActivity">
<intent-filter>
<actionandroid:name="android.intent.action.MAIN"/>
<categoryandroid:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
</application>
</manifest>
7. We'rereadytoupdatetheMainApplicationJavaclass.ThegetUseDeveloperSupport
methodherewillenablethedevelopmentmenu.ThegetPackagesmethodis
alistofpackagesusedbytheapp,andonlyincludesMainReactPackage()since
weareonlyusingthemainReactpackage.ThegetJSMainModuleNamemethod
returnstheindex.androidstring,whichreferstotheindex.android.jsfileinthe
ReactNativelayer:
importandroid.app.Application;
importcom.facebook.react.ReactApplication;
importcom.facebook.react.ReactNativeHost;
importcom.facebook.react.ReactPackage;
importcom.facebook.react.shell.MainReactPackage;
importjava.util.Arrays;
importjava.util.List;
publicclassMainApplicationextendsApplicationimplementsReactApplication{
privatefinalReactNativeHostmReactNativeHost=newReactNativeHost(this){
@Override
publicbooleangetUseDeveloperSupport(){
returnBuildConfig.DEBUG;
}
@Override
protectedList<ReactPackage>getPackages(){
returnArrays.<ReactPackage>asList(
newMainReactPackage()
);
}
};
@Override
publicReactNativeHostgetReactNativeHost(){
returnmReactNativeHost;
}
@Override
protectedStringgetJSMainModuleName(){
return"index.android";
}
}
8. Next,let'screateanothernewJavaclasswiththenameReactFragment.This
classneedsthreemethods:OnAttachiscalledwhenthefragmentisattached
tothemainactivity,OnCreateViewinstantiatestheviewforthefragment,and
OnActivityCreatediscalledwhentheactivityisbeingcreated:
importandroid.app.Fragment;
importandroid.content.Context;
importandroid.os.Bundle;
importandroid.view.LayoutInflater;
importandroid.view.ViewGroup;
importcom.facebook.react.ReactInstanceManager;
importcom.facebook.react.ReactRootView;
publicabstractclassReactFragmentextendsFragment{
privateReactRootViewmReactRootView;
privateReactInstanceManagermReactInstanceManager;
//Thismethodreturnsthenameofourtop-levelcomponenttoshow
publicabstractStringgetMainComponentName();
@Override
publicvoidonAttach(Contextcontext){
super.onAttach(context);
mReactRootView=newReactRootView(context);
mReactInstanceManager=
((EmbedApp)getActivity().getApplication())
.getReactNativeHost()
.getReactInstanceManager();
}
@Override
publicReactRootViewonCreateView(LayoutInflaterinflater,ViewGroupgroup,BundlesavedInstanceState){
super.onCreate(savedInstanceState);
returnmReactRootView;
}
@Override
publicvoidonActivityCreated(BundlesavedInstanceState){
super.onActivityCreated(savedInstanceState);
mReactRootView.startReactApplication(
mReactInstanceManager,
getMainComponentName(),
getArguments()
);
}
}
10. Finally,createaJavaclasscalledEmbedFragmentthatwillextendReactFragment:
importandroid.os.Bundle;
publicclassEmbedFragmentextendsReactFragment{
@Override
publicStringgetMainComponentName(){
return"EmbedApp";
}
}
11. Let'sopenMainActivity.javaandaddimplementsDefaultHardwareBackBtnHandlerto
theclassdefinitionforhandlinghardwarebackbuttonevents.Youcanview
theannotatedsourcecodeforthisReactNativeclasshere:https://github.com/
facebook/react-native/blob/master/ReactAndroid/src/main/java/com/facebook/react/modu
les/core/DefaultHardwareBackBtnHandler.java.
12. We'llalsobeaddingafewmethodstotheclass.TheonCreatemethodwillset
thecontentviewtotheMainActivityandaddaFABbuttonthat,when
clicked,willinstantiateanewinstanceoftheEmbedFragmentwedefinedinstep
10.ThatinstanceofEmbedFragmentisusedbythefragmentmanagertoaddthe
ReactNativeapptotheview.Theremainingmethodshandletheeventsthat
occurwhenthedevice'ssystembuttonsarepressed(suchastheback,
pause,andresumebuttons):
importandroid.app.Fragment;
importandroid.os.Bundle;
importandroid.support.design.widget.FloatingActionButton;
importandroid.support.v7.app.AppCompatActivity;
importandroid.support.v7.widget.Toolbar;
importandroid.view.KeyEvent;
importandroid.view.View;
importcom.facebook.react.ReactInstanceManager;
importcom.facebook.react.modules.core.DefaultHardwareBackBtnHandler;
publicclassMainActivityextendsAppCompatActivityimplementsDefaultHardwareBackBtnHandler{
privateReactInstanceManagermReactInstanceManager;
@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toolbartoolbar=(Toolbar)findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
FloatingActionButtonfab=(FloatingActionButton)findViewById(R.id.fab);
fab.setOnClickListener(newView.OnClickListener(){
@Override
publicvoidonClick(Viewview){
FragmentviewFragment=newEmbedFragment();
getFragmentManager().beginTransaction().add(R.id.reactnativeembed,viewFragment).commit();}
});
mReactInstanceManager=((EmbedApp)getApplication()).getReactNativeHost().getReactInstanceManager();
}
@Override
publicvoidinvokeDefaultOnBackPressed(){
super.onBackPressed();
}
@Override
protectedvoidonPause(){
super.onPause();
if(mReactInstanceManager!=null){
mReactInstanceManager.onHostPause(this);
}
}
@Override
protectedvoidonResume(){
super.onResume();
if(mReactInstanceManager!=null){
mReactInstanceManager.onHostResume(this,this);
}
}
@Override
protectedvoidonDestroy(){
super.onDestroy();
if(mReactInstanceManager!=null){
mReactInstanceManager.onHostDestroy(this);
}
}
@Override
publicvoidonBackPressed(){
if(mReactInstanceManager!=null){
mReactInstanceManager.onBackPressed();
}else{
super.onBackPressed();
}
}
@Override
publicbooleanonKeyUp(intkeyCode,KeyEventevent){
if(keyCode==KeyEvent.KEYCODE_MENU&&mReactInstanceManager!=null){
mReactInstanceManager.showDevOptionsDialog();
returntrue;
}
returnsuper.onKeyUp(keyCode,event);
}
}
13. Thelaststepistoaddsomesettingsforthelayoutwhenthefragmentis
loaded.We'llneedtoeditthecontent_main.xmlfile,whichislocatedinthe/res
folder.Thisisthemaincontentoftheview.Itholdsthecontainerview
(FrameLayout)thatwewillattachthefragmentto,andtheothernative
elementsshouldbedisplayed:
<FrameLayout
android:layout_width="match_parent"
android:layout_height="300dp"
android:layout_centerVertical="true"
android:layout_alignParentStart="true"
android:id="@+id/reactnativeembed"
android:background="#FFF">
</FrameLayout>
14. IntheTerminal,runthefollowingcommand:
react-nativestart
ThisbuildsandhoststheReactNativeapp.Now,wecanopentheappin
theAndroidemulator.YouwillseethefollowingafterpressingtheFAB
button:
Howitworks...
ToaccomplishrenderingReactNativeinsideofourAndroidapplication,wehad
toperformafewsteps.First,wehadtodefineanApplicationclassthat
implementstheReactApplicationinterface.Then,wehadtocreateaFragmentthat
wouldberesponsibleforinstantiatingandrenderingtheReactRootView.Witha
fragment,weareabletorendertheReactNativeviewinourMainActivity.Inthis
recipe,weaddedthefragmenttoourfragmentcontainerview.Thisessentially
replacesalloftheapplicationcontentwiththeReactNativeapplication.
Wecoveredalotofintegrationcodeinthisrecipe.Foramorein-depthlookat
howeachofthesepieceswork,Iencourageyoutoreadtheofficial
documentationathttps://facebook.github.io/react-native/docs/integration-with-existing
-apps.html.
CommunicatingfromanAndroidapp
toReactNative
NowthatwehavecoveredhowtorenderourReactNativeappinsidean
AndroidappintheEmbeddingaReactNativeappinsideaNativeAndroid
apprecipe,weneedtotakethattothenextlevel.OurReactNativeapplication
shouldbemorethanadummyUI.Itshouldbeabletoreacttoactionsthatare
goingoninitsparentapplication.
Inthisrecipe,wewillaccomplishsendingdatafromourAndroidapplicationto
ourembeddedReactNativeapp.TheReactNativeapplicationcanacceptdata
whenitisfirstinstantiated,andthenatruntime.Wewillbecoveringhowto
accomplishbothmethods.ThisrecipewilluseEditTextintheAndroidappand
setupone-waybindingtotheReactNativeapp.
Gettingready
Forthisrecipe,pleaseensurethatyouhaveanAndroidappwithaReactNative
appembedded.Ifyouneedguidancetoaccomplishthis,pleasecomplete
theEmbeddingaReactNativeappinsideaNativeAndroidapprecipe.
Howtodoit...
1. OpenAndroidStudioinyourProjectandopencontent_main.xml.
2. PresstheTexttabonthebottomtoopenthesourceeditorandadd/replace
thefollowingnodes:
<TextViewandroid:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="PresstheMailIcontostarttheReactNativeapplication"
android:id="@+id/textView"/>
<FrameLayoutandroid:layout_width="match_parent"
android:layout_height="300dp"
android:layout_centerVertical="true"
android:layout_alignParentStart="true"
android:id="@+id/reactnativeembed"
android:background="#FFF">
</FrameLayout>
<LinearLayoutandroid:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="75dp"
android:layout_below="@+id/textView"
android:layout_centerHorizontal="true">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="UserName:"
android:id="@+id/textView2"
android:layout_weight="0.14"/>
<EditTextandroid:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/userName"
android:layout_weight="0.78"
android:inputType="text"
android:singleLine="true"
android:imeOptions="actionDone"/>
</LinearLayout>
3. OpenMainActivity.javaandaddthefollowingclassfields:
privateReactInstanceManagermReactInstanceManager;
privateEditTextuserNameField;
privateBooleanisRNRunning=false;
4. InsidetheonCreatemethod,settheuserNameFieldpropertywiththefollowing
code:
userNameField=(EditText)findViewById(R.id.userName);
5. ReplaceFloatingActionButtononClickListenerwiththefollowing:
fab.setOnClickListener(newView.OnClickListener(){
@OverridepublicvoidonClick(Viewview){
FragmentviewFragment=newEmbedFragment();
if(userNameField.getText().length()>0){
BundlelaunchOptions=newBundle();
launchOptions.putString("userName",
userNameField.getText().toString());
viewFragment.setArguments(launchOptions);
}
getFragmentManager().beginTransaction().add(R.id.reactnativeembed,viewFragment).commit();
isRNRunning=true;
}
});
6. Next,weneedtoaddaTextChangedListenertoouruserNameFieldin
theonCreatemethod:
userNameField.addTextChangedListener(newTextWatcher(){
@OverridepublicvoidbeforeTextChanged(CharSequences,intstart,intcount,intafter){}
@OverridepublicvoidonTextChanged(CharSequences,intstart,intbefore,intcount){}
@OverridepublicvoidafterTextChanged(Editables){
if(isRNRunning){
sendUserNameChange(s.toString());
}
}
});
7. ThelastchangeweneedtomakeforourActivityistoaddmethodsthatwill
sendtheeventacrosstheReactNativebridge:
privatevoidsendUserNameChange(StringuserName){
WritableMapparams=Arguments.createMap();
params.putString("userName",userName);
sendReactEvent("UserNameChanged",params);
}
privatevoidsendReactEvent(StringeventName,WritableMapparams){
mReactInstanceManager.getCurrentReactContext()
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
.emit(eventName,params);
}
8. Let'sreturntotheJavaScriptlayerandaddthefollowingcodetotheApp
classimplementation:
importReact,{Component}from'react';
import{
AppRegistry,
StyleSheet,
View,
Text,
NativeAppEventEmitter
}from'react-native';
exportdefaultclassEmbedAppextendsComponent<{}>{
componentWillMount(){
this.setState({
userName:this.props.userName
});
NativeAppEventEmitter.addListener('UserNameChanged',(body)=>{
this.setState({userName:body.userName});
});
}
render(){
return(
<Viewstyle={styles.container}>
<Text>Hello{this.state.userName}</Text>
</View>
);
}
}
conststyles=StyleSheet.create({
container:{
flex:1,
justifyContent:'center',
alignItems:'center',
backgroundColor:'#F5FCFF',
},
welcome:{
fontSize:20,
textAlign:'center',
margin:10,
},
instructions:{
textAlign:'center',
color:'#333333',
marginBottom:5,
},
});
AppRegistry.registerComponent('EmbedApp',()=>EmbedApp);
9. Now,ifyouruntheapplication,youcanentertextintheUserNamefield
andstarttheReactNativeapplication:
Howitworks...
Inthisrecipe,werenderedthefragmentasaninlineview.Instep2,weaddedan
emptyFrameLayoutthatwetargetedinstep5torenderthefragment.Thebinding
functionalitywasaccomplishedbyusingtheReactNativebridgevia
RCTDeviceEventEmitter.Thiswasoriginallydesignedtobeusedwithnativemodules,
butaslongasyouhaveaccesstotheReactContextinstance,youcanuseitforany
communicationwiththeReactNativeJavaScriptlayer.
CommunicatingfromReactNativeto
anAndroidappcontainer
Aswediscussedinthepreviousrecipe,itisextremelybeneficialforour
embeddedapplicationtobeawareofwhat'sgoingonaroundit.Weshouldalso
makeaneffortsothatourAndroidparentapplicationcanbeinformed
aboutwhatgoesoninsidetheReactNativeapplication.Theapplicationshould
notonlybeabletoperformbusinesslogic–itshouldbeabletoupdateitsUIto
reflectchangesintheembeddedapp.
ThisrecipeshowsushowtoleveragenativemodulestoupdatethenativeUI
that'screatedinsidetheAndroidapplication.Wewillhaveatextfieldinour
ReactNativeappthatupdatesatextfieldthatisrenderedinthehostAndroid
application.
Gettingready
Forthisrecipe,pleaseensurethatyouhaveanAndroidapplicationwithaReact
Nativeappembedded.Ifyouneedguidancetoaccomplishthis,pleasecomplete
theEmbeddingaReactNativeappinsideaNativeAndroidapprecipe.
Howtodoit...
1. OpenAndroidStudiotoyourProjectandopencontent_main.xml.
2. PresstheTexttabonthebottomtoopenthesourceeditorandadd/replace
thefollowingnodes:
<?xmlversion="1.0"encoding="utf-8"?>
<RelativeLayoutxmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
tools:context="com.embedapp.MainActivity"
tools:showIn="@layout/activity_main">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="PresstheMailIcontostarttheReactNativeapplication"
android:id="@+id/textView"/>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="300dp"
android:layout_centerVertical="true"
android:layout_alignParentStart="true"
android:id="@+id/reactnativeembed"
android:background="#FFF"></FrameLayout>
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="75dp"
android:layout_below="@+id/textView"
android:layout_centerHorizontal="true">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="UserName:"
android:id="@+id/textView2"
android:layout_weight="0.14"/>
<EditText
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/userName"
android:layout_weight="0.78"
android:inputType="text"
android:singleLine="true"
android:imeOptions="actionDone"/>
</LinearLayout>
</RelativeLayout>
3. CreateaJavaclassnamedUserNameManager.Thiswillbeanativemodulethat
willservethepurposeofupdatingtheEditTextfieldweaddedtothelayout.
IfyouarenotfamiliarwithcreatinganativemoduleforReactNative,pleasereferto
theExposingcustomAndroidmodulesrecipeinChapter11,AddingNativeFunctionality.
4. MostoftheworkinUserNameManager.javaisbeingdoneinthesetUserName
method.Here,theAndroidlayerupdatesthetextcontentsoftheviewbased
onwhatit'ssentfromtheReactNativelayer.TheReactmethodisn't
necessarilygoingtorunonthemainUIthread,sowe
usemainActivity.runOnUiThreadtoupdatetheviewwhenthemainUIthreadis
ready:
publicclassUserNameManagerextendsReactContextBaseJavaModule{
publicUserNameManager(ReactApplicationContextreactApplicationContext){
super(reactApplicationContext);
}
@OverridepublicStringgetName(){
return"UserNameManager";
}
@ReactMethodpublicvoidsetUserName(finalStringuserName){
ActivitymainActivity=getReactApplicationContext().getCurrentActivity();
finalEditTextuserNameField=(EditText)mainActivity.findViewById(R.id.userName);
mainActivity.runOnUiThread(newRunnable(){
@Overridepublicvoidrun(){
userNameField.setText(userName);
}
});
}
}
5. ToexporttheUserNameManagermodule,we'llneedtoedittheUserNamePackage
Javaclass.WecanexportittotheReactNativelayerbycallingmodules.add,
passinginanewUserNameManagerthattakesthereactContextasaparameter:
publicclassUserNamePackageimplementsReactPackage{
@OverridepublicList<Class<<?extendsJavaScriptModule>>createJSModules(){
returnCollections.emptyList();
}
@OverridepublicList<ViewManager>createViewManagers(ReactApplicationContextreactContext){
returnCollections.emptyList();
}
@OverridepublicList<NativeModule>createNativeModules(ReactApplicationContextreactContext){
List<NativeModule>modules=newArrayList<>();
modules.add(newUserNameManager(reactContext));
returnmodules;
}
}
6. AddtheUserNamePackageinthegetPackagesmethodinMainApplication:
@Override
protectedList<ReactPackage>getPackages(){
returnArrays.<ReactPackage>asList(
newMainReactPackage(),
newUserNamePackage()
);
}
7. Now,weneedtohaveourReactNativeUIrenderaTextFieldandcall
ourUserNameManagernativemodule.Openindex.android.jsandimport
theTextInputandNativeModulesmodulesfrom'react-native'.
8. CreateavariablereferencefortheUserNameManager:
constUserNameManager=NativeModules.UserNameManager;
9. TheReactNativeappwillsimplyneedaTextInputformanipulatinga
userNamepropertyonthestateobject:
letstate={
userName:''
}
onUserNameChange=(userName)=>{
this.setState({
userName
});
UserNameManager.setUserName(userName);
}
render(){
return(
<Viewstyle={styles.container}>
<Text>EmbeddedRNApp</Text>
<Text>EnterUserName</Text>
<TextInputstyle={styles.userNameField}
onChangeText={this.onUserNameChange}
value={this.state.userName}
/>
</View>
);
}
10. Afterrunningtheapplication,startingtheReactNativeembeddedapp,and
addingtexttothetextfield,youshouldseesomethingsimilartowhat's
showninthefollowingscreenshot:
Howitworks...
TogetourReactNativeapptoupdatethenativeappcontainers,wecreateda
nativemodule.ThisistherecommendedwayofcommunicatingfromJavaScript
tothenativelayer.However,sincewehadtoupdateanativeUIcomponent,the
operationhadtobeperformedonthemainthread.Thisisachievedbygettinga
referencetoMainActivityandcallingtherunOnUiThreadmethod.Thisisdonein
thesetUserNamemethodofstep4.
Handlingbeinginvokedbyan
externalAndroidapp
Earlierinthischapter,wecoveredhowtohandleinvocationfromanexternal
appiniOSintheHandlingbeinginvokedbyanexternaliOSapprecipe.Inthis
recipe,we'llcoverthesameconceptofdeeplinkinginAndroid.
Howtodoit...
1. Let'sbeginbyopeningtheReactNativeAndroidprojectinAndroidStudio
andnavigatingtoAndroidManifest.xml.
2. Forourexample,wewillregisterourapplicationunderinvoked://scheme.
We'llupdatethe<activity>nodetothefollowing:
<activity
android:name=".MainActivity"
android:label="@string/app_name"
android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
android:windowSoftInputMode="adjustResize"
android:launchMode="singleTask">
<intent-filter>
<actionandroid:name="android.intent.action.MAIN"/>
<categoryandroid:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
Formoreinformationonhowthisintent-filterworks,refertotheofficialAndroid
documentationathttps://developer.android.com/training/app-links/deep-linking.
3. Next,we'llneedtocreateasimpleReactNativeappwhoseUIreactsto
beinginvoked.Let'sopentheindex.android.jsfile.We'llstartby
importingtheLinkingmoduleintheimportblockfrom'react-native':
importReactfrom'react';
import{Platform,Text,Linking}from'react-native';
4. Let'sbuildouttheAppclassfortheReactNativeapp.Whenthecomponent
mounts,we'llregisteraLinkingeventlistenerwithaneventwe'llnameurl.
Whenthiseventoccurs,onAppInvokedwillbefired,updatingthestatus
propertyofstate,alongwiththeeventthat'spassedtothecallback:
exportdefaultclassAppextendsReact.Component{
state={
status:'AppRunning'
}

componentWillMount(){
Linking.addEventListener('url',this.onAppInvoked);
}

componentWillUnmount(){
Linking.removeEventListener('url',this.onAppInvoked);
}

onAppInvoked=(event)=>{
this.setState({status:`AppInvokedby${event.url}`});
}

render(){
return(
<Viewstyle={styles.container}>
<Textstyle={styles.instructions}>
AppStatus:
</Text>
<Textstyle={styles.welcome}>
{this.state.status}
</Text>
</View>
);
}
}
5. Runningtheapplicationandinvokingitfromanotherappwilllook
somethinglikethis:
Howitworks...
Inthisrecipe,weregisteredourURLschemaforlinkingbyediting
theAndroidManifest.xmlfileinstep2.Animportantthingtonoteisthechangeof
thelaunchModetosingleTask.Thispreventstheoperatingsystemfromcreating
multipleinstancesofourReactactivity.Thisisimportantifyouwanttobeable
toproperlycapturethedatathat'spassedalongwiththeintent.
DeployingYourApp
Inthischapter,wewillcoverthefollowingrecipes:
DeployingdevelopmentbuildstoaniOSdevice
DeployingdevelopmentbuildstoanAndroiddevice
DeployingtestbuildstoHockeyApp
DeployingiOStestbuildstoTestFlight
DeployingproductionbuildstotheAppleAppStore
DeployingproductionbuildstotheGooglePlayStore
DeployingOver-The-Airupdates
OptimizingReactNativeappsize
Introduction
Ifyou'reanindependentdeveloper,you'relikelytogothroughafewdifferent
stagesofdevelopment.Thefirststagewillfindyoutestingyourapponyour
personaliOSorAndroiddevice.Afterexhaustingthisstage,you'reprobably
goingtowanttoshareitwithaselectgroupofpeopletogetuserfeedback.
Eventually,you'regoingtoreachapointwhereyourappisreadytoberelease
intotheworldviaappstores.Thischapterwillwalkthrougheachoneofthese
stagesandcoverpushingupdatestoyourapp,alongwithafewoptimization
tips.
Deployingdevelopmentbuildstoan
iOSdevice
Duringdevelopment,you'lllikelyspendmuchofyourtimetestingyouriOSapp
usingtheiOSSimulatorthatcomesinstalledwithXcode.WhiletheiOS
Simulatorisbyfarthebestperformingandclosestmethodtorunningour
applicationonaniOSdevice,it'sstillnotthesameastherealthing.TheiOS
Simulatorusesthecomputer'sCPUandGPUtorenderthesimulatedOS,so
dependingonyourdevelopmentmachine,itmayendupperformingbetter(or
worse)thantheactualdevice.
Thankfully,Expo'sabilitytotestrunningcodeonanactualdevicecomesone
stepclosertotherealendproduct,buttherearestilldifferencesbetweenafinal
appandadevelopmentapprunninginExpo.Andifyou'rebuildingapureReact
Nativeapp,youwon'thavetheluxuryofusingExpotoeasilyruntheappona
device.
Eitherway,you'lleventuallywanttotesttherealapponaphysicaldevicesoyou
canexperiencetheactualUXandperformanceoftheendproduct.
Inthisrecipe,wewillwalkyouthroughtakingaReactNativeappand
deployingittoaniPhoneoriPad.
Gettingready
We'lljustneedanewpureReactNativeapp,whichwe'llnameTestDeployApp.You
cancreatetheappviathefollowingcommand:
react-nativeinit
Also,makesureyouriOSdeviceisconnectedtoyourdevelopmentmachinevia
USB.
Howtodoit...
1. Let'sfirstopenthenewlycreatedReactNativeiOSprojectinXcode.Open
theProjectEditorbyselectingtherootoftheprojectintheleftpanel.
2. UndertheGeneraltaboftheProjectEditor,selecttheiOSappinthe
TARGETSsectionontheleft.UndertheSigningsection,selectyourTeam,
asfollows:
3. RepeatthisstepfortwoeachoftheentriesintheTARGETSlist.
4. Selectyourdeviceinthedestinationselector,asfollows:
5. Tostartrunningtheapponyourconnecteddevice,justpressthePlay
button.You'llhavetomakesureyourdeviceispluggedin,unlocked,and
trustedforittoshowupinthedeviceslistinXcode.Ifthisisthefirsttime
runninganappyou'vedevelopedonthisdevice,you'llalsoneedtoadjust
thesettingstotrustappsfromyourdeveloperaccount.OntheiOSdevice,
thissettingcanbefoundinSettings|General|DeviceManagement.
Howitworks...
Deployingourdevelopmentbuildtothedevicesimplyinvolvesdesignatinga
Team,thenrunningtheappasyouwouldforuseontheXcodesimulator,but
targetingthepluggedindeviceinstead.Weusethelocalhostpackagertocreate
ourbundlefile.Thisfilethengetssavedlocallyonthedeviceforthefuture.
Notethat,sincethisisadevelopmentbuild,thecodeisnotyetasoptimizedasit
willbeinafinalrelease.Youwillseeasignificantperformanceincreasewhen
movingtoaproductionrelease.
Deployingdevelopmentbuildstoan
Androiddevice
WhiledevelopinganAndroidapplication,you'llmostoftenprobablyberunning
theapponanAndroidemulatorsuchasGenymotion.Whileconvenient,an
emulatorwillhavepoorperformancewhencomparedwitharealAndroid
device.
ThebestwaytotestanappistouseaphysicalAndroiddevice.Thisrecipewill
walkthroughdeployingaReactNativeapptoaphysicalAndroiddevice.
Gettingready
We'lljustneedanewpureReactNativeapp,whichwe'llnameTestDeployApp.You
cancreatetheappviathiscommand:
react-nativeinit
Also,makesureyouriOSdeviceisconnectedtoyourdevelopmentmachinevia
USB.
Howtodoit...
1. Let'sstartbyopeningourReactNativeAndroidprojectinAndroidStudio.
2. Next,presstherunbutton,asfollows:
3. MakesuretheChoosearunningdeviceradiobuttonisselected,andthat
yourdeviceisdisplayedinthelist.PressOKtocontinue,asfollows:
There'smore...
TheReactNativepackagershouldstartwhenyouruntheapplication.Ifit
doesn't,you'llhavetomanuallystartthepackager.Ifyouseeanerrorscreen
withthemessageCouldnotgetBatchedBridge,pleasemakesureyourbundleis
packagedcorrectlyorCouldnotconnecttodevelopmentserver,youshouldbe
abletofixthisbyrunningthefollowingcommandintheTerminal:
adbreversetcp:8081tcp:8081
Howitworks...
MuchlikeXcode,wecanrunourappbysimplyplugginginarealdevice,
pressingRun,andselectingthedevicetheappshouldrunon.Theonly
complicationthatmightariseissettingupcommunicationbetweenthedevice
andthedevelopmentmachine.Runthiscommand:
adbreverse
Thisestablishesaportforwardfromthedevicetothehostcomputer.Thisisa
developmentbuild,andthecodeisnotyetoptimized,sotherewillbea
performanceincreaseoncetheappisbuiltasaproductionrelease.
DeployingtestbuildstoHockeyApp
Beforereleasinganappintothewild,it'simportanttostresstestyourappandto
getuserfeedbackwhenpossible.Toaccomplishthis,youneedtocreateasigned
buildofyourappthatyoucansharewithagroupoftestusers.Forarobusttest
build,you'llneedtwothings:analytics/reportingonappperformance,and
amechanismfordelivery.HockeyAppprovidesthisandmoreforyourtest
buildsonbothiOSandAndroid.
ThisrecipewillwalkthroughdeployingaReactNativeapptoHockeyAppfor
testingpurposes.WewillwalkthroughbothiOSandAndroidreleases.
Gettingready
Forthisrecipe,wewillbeusingthesameempty,pureReactNativeappfromthe
lasttworecipes,whichwenamedTestDeployApp.ForiOSdeployments,youwill
needtobeenrolledintheAppleDeveloperProgram,andyou'llneedtohave
cocoapodsinstalled.Theeasiestwaytoinstallcocoapodsistousehomebrew,viathis
command:
brewinstallcocoapods
You'llalsoneedtohaveaHockeyAppaccount,whichyoucansignupforat
theirwebsiteat:
https://hockeyapp.net/
Howtodoit...
1. First,weneedtoinstallthereact-native-hockeyappmoduleinourapplication.
OpentheTerminal,gotoyourapplication'srootprojectdirectory,andenter
thefollowingcommand:
$npminstallreact-native-hockeyapp--save
2. Gointoyourios/directoryandinitializeyourPodfile:
$podinit
3. OpenyourPodfileandaddpod"HockeySDK"toyourtarget.
4. BackintheTerminal,installthePodfile,asfollows:
$podinstall
5. Now,let'sopenupXcodeandopenourReactNativeproject:
(ios/TestDeployApp.xcodeproj).
6. IrecommendchangingyourBundleIdentifiertosomethingmore
meaningfulthanthedefault,sopleasechangeitinyourGeneralSettings
dialog,asfollows:
7. Draganddrop./ios/Pods/Pods.xcodeprojintotheLibrariesgroupinyour
projectnavigator,asfollows:
8. DraganddroptheRNHockeyApp.handRNHockeyApp.mfileslocatedin
./node_modules/react-native-hockeyapp/RNHockeyApp/RNHockeyAppintothesame
Librariesgroup.
9. Next,we'llgototheHockeyAppsiteandcreateourappthere.Loginand
clicktheNewApp.
10. Sincewedonothaveourbuildreadyyet,clickmanuallyinthephrase
Don'twanttouploadabuild?Createtheappmanuallyinsteadinthe
followingmodal.
11. WhenfillingoutthefieldsintheCreateAppform,besuretomatchthe
TitleandBundleIdentifierthatwedefinedearlierinstep6,thenpressSave,
asfollows:
12. MakeanoteoftheAppIDsincewe'llbeusingitinthenextstep.
13. OpenApp.jsandaddthefollowingcode:
importHockeyAppfrom'react-native-hockeyapp';
exportdefaultclass
TestDeployAppextendsComponent{
componentWillMount(){
HockeyApp.configure(YOUR_APP_ID_HERE,true);
}
componentDidMount(){
HockeyApp.start();
HockeyApp.checkForUpdate();
}
}
14. BackinXcode,setGenericiOSDeviceasyourdestinationtargetandbuild
(Product|Build)theapp,asfollows:
15. Now,weneedtocreateour.ipafile.ThiscanbedonefromtheXcodemenu
viaProduct|Archive.
16. ThiswillopentheArchiveslist.PresstheDistributeAppbuttontostartthe
processofcreatingthe.ipa.
17. SelecttheDevelopmentoptionandpressNext.
18. Yourprovisioningteamshouldautomaticallybeselected.Withthecorrect
Teamselected,pressNext.
19. LeavethedefaultExportsettingsandpressNext.Onthesummarypage,
alsopressNext.
20. SelectthedestinationdirectoryandpressExport.
21. BackintheHockeyAppbrowserwindow,clickAddVersion.
22. Dragthe.ipafilewejustexportedintothemodalwindow.
23. Wecanleavethesettingsheresettotheirdefaults,socontinuepressing
Nextuntilthelastmodalscreen,thenpressDoneatthesummaryscreen.
That'sitfortheiOSapp.YoucanadduserstoyourHockeyAppapp,and
yourtestersshouldthenbeabletodownloadyourapp.Let'sswitchoverto
theAndroidsideofthings.OpenAndroidStudio,thenopentheAndroid
folderinourReactNativeprojectat:
https://support.hockeyapp.net/kb/client-integration-android/hockeyapp-for-android-sdk
24. Repeatstep8tostep11,changingthePlatformtoAndroid,asfollows:
25. Now,weneedtobuildour.apkfile.Youcanfindthemostup-to-date
methodforbuildingthe.apkintheReactNativedocumentation,locatedat:
https://facebook.github.io/react-native/docs/signed-apk-android.html
26. Repeatstep21andstep22forthe.apkgeneratedfromourAndroidproject.
Howitworks...
Forthisrecipe,weusedHockeyAppforitstwomainfeatures:itsbeta
distributionanditsHockeySDK(whichsupportscrashreporting,metrics,
feedback,authentication,andnotificationsforupdates).ForiOS,beta
distributionisdonethroughtheOTAenterprisedistributionmechanismhosted
byHockeyApp.Whenyousignyourapp,youcontrolwhichdevicescanopenit.
HockeyAppjustsendsnotificationsandprovidestheURLforbetatesters
todownloadyourappthroughitsenterpriseappstore.Androidissimplersince
thereisnoneedtoworryabouthowappsaretransferred.Thismeans
HockeyApphoststhe.apkfileonawebserverthattesterscandownloadand
install.
DeployingiOStestbuildsto
TestFlight
BeforeHockeyAppcamealong,theserviceforbetatestingmobileappswas
TestFlight.Infact,itwassogoodatdoingjustthat,thatApplepurchasedits
parentcompanyandintegrateditintoiTunesConnect.TestFlightnowservesas
theofficialapptestingplatformforApple.Thereareafewdifferencesbetween
TestFlightandHockeyApptoconsider.Firstandforemost,TestFlightbecame
iOSonlywhenitwaspurchasedbyApple.Second,therearetwostylesof
testinginTestFlight:internalandexternal.Internaltestinginvolvessharingthe
applicationwithDeveloperorAdminrolemembersofyourteam,andlimits
distributionto25testersacross10deviceseach.Externaltestingallowsyouto
inviteupto2,000testerswhodonothavetobemembersofyourorganization.
Thisalsomeansthatthesetestersdonotuseupyourdevicequota.External
testingapplicationsgothroughtheBetaAppReviewperformedbyApple,
whichisnotquiteasrigorousasApple'sreviewforreleasinganapptotheApp
Store,butitisagoodfirstpass.
ThisrecipefocusesontakingourReactNativeappanddeployingatestbuildto
TestFlight.Wewillbesettingupaninternaltest,sincewedonotwantApple
reviewingourexampleReactNativeapp,buttheprocedureisthesameforboth
internalandexternaltesting.
Gettingready
Forthisrecipe,wewillbeusingthesameboilerplateReactNativeappfrom
previousrecipes,whichwe'venamedTestDeployApp.Youwillalsoneedtobe
enrolledintheAppleDeveloperProgram,you'llneedtohaveyourdevelopment
anddistributioncertificatessetupinXcode,andyourappwillneedtohaveits
AppIconset.
Howtodoit...
1. Let'sstartbyopeningourprojectinXcodevia
theios/TestDeployApp.xcodeprojfile.
2. Asstatedinthelastrecipe,Ialsorecommendchangingyour
BundleIdentifiertosomethingmoremeaningfulthanthedefault,for
example:
3. Next,let'slogintotheAppleDeveloperProgramandnavigatetotheApp
IDregistrationpage,locatedathttps//:developer.apple.com/account/ios/identifie
r/bundle.
4. Here,fillouttheNameandBundleIDforyourproject,thenpress
theContinuebutton,followedbytheRegisterbutton,andfinallytheDone
buttontocompleteregistrationoftheapp.
5. Next,we'lllogintotheiTunesConnectsite,locatedathttps://itunesconnect.a
pple.com.
6. IniTunesConnect,navigatetoMyApps,thenpressthePlus(+)buttonand
selectNewApptoaddanewapp.
7. IntheNewAppdialog,fillouttheNameandLanguage.Selectthe
BundleIDtomatchtheoneyoucreatedpreviously,andaddauniqueapp
referenceintheSKUfield,thenpressCreate.
8. Next,navigatetotheTestFlightsectionforyourappandbesuretofillout
theLocalizableInformationsection.
9. Let'sreturntoXcodetocreatethe.ipafile.SelectGenericiOSDevicefor
theactivescheme,thencreatethefileviatheXcodemenu
(Product|Archive).ThiswillopentheArchiveslist,whereyoucanpress
theUploadtoAppStorebuttontouploadtheapp.
10. Yourprovisioningteamshouldautomaticallybeselected.Besurethe
correctteamisselectedandpressChoose.Oncethearchiveiscreated,press
theUploadbutton.
11. Afteruploadingtheapp,you'llneedtowaituntilyoureceiveanemailfrom
iTunesConnectinformingyouthatthebuildhascompletedprocessing.
Onceprocessingiscomplete,youcanreturntotheiTunesConnectpage
andopentheInternalTestingview.
12. IntheInternalTestingsection,clickSelectVersiontoTestandselectyour
build,thenclicktheNextbutton.AttheExportCompliancescreen,press
OK.
13. We'rereadytoaddinternaltesters.Selecttheusersyouwouldliketotest
theapp,thenclicktheStartTestingbuttonandconfirmyourselectioninthe
followingmodal.Yourusersshouldnowgetaninvitationemailtotestyour
app!
Howitworks...
TestFlightservesasafirst-classcitizenintheAppStorepublishingpipeline.
Applehasintegrateditssupportforapplicationbetatestingdistributiondirectly
intoiTunesConnect,creatingasmoothandseamlessprocessfordevelopers.
ThisprocedureislargelythesameasdeployingtotheAppStore,exceptthat
whenusingiTunesConnect,youmustenableandconfiguretesting.
Itisaseamlessexperienceforthetesteraswell.Assoonasyouaddtestusersin
iTunesConnect,theyarenotifiedtoinstalltheTestFlightapp,wherethey
willhaveeasyaccesstotheappstheycantest.TestFlightalsomakestheprocess
easierfordevelopersbynotrequiringthemtoaddanyextrathird-partylibraries
orcodetosupportTestFlight,aswouldbeneededwithHockeyApp.
Deployingproductionbuildstothe
AppleAppStore
Onceyou'vethoroughlytestedyourapp,you'rereadytomoveontothenext
(andlikelythemostexciting)stepintheiOSappmakingprocess:releasingto
theAppleAppStore.
Thisrecipewillwalkthroughtheprocessofpreparingyourproductionbuildand
submittingittotheAppleAppStore.Wewon'tactuallybesubmittingtheappto
thestore,sincewe'reworkingwithanexampleappinsteadofaproduction-ready
one.Thelastfewstepsintheprocess,however,areverystraightforward.
Gettingready
Forthisrecipe,wewillagainbeusingthesimpleReactNativeexampleapp
fromearlierrecipes,TestDeployApp.You'llofcoursealsoneedtobeenrolledinthe
AppleDeveloperProgram,andhaveyourdevelopmentanddistribution
certificatessetupinXcodeasdiscussedearlierinthischapter.Forareal
productionappdeployment,youwillalsoneedtohaveboththeAppIconsetand
screenshotsoftheappreadyforuseiniTunes.
Howtodoit...
1. Let'sstartbyopeningupXcodeusingtheios/TestDeployApp.xcodeprojfile.
2. Asstatedbefore,it'srecommendedthatyouchangeyourBundleIdentifier
tosomethingmoremeaningfulthanthedefault,sobesuretochangeitin
theGeneralSettingsdialog.
3. It'salsoagoodideatotestyourappinProductionModeonyourdevice.
Thiscanbedonebychangingyourappscheme'sBuildConfiguration
(foundviatheProduct|Scheme|EditSchememenus)toRelease,as
follows:
4. Next,you'llneedtoregistertheappontheAppIDregistrationpage,
locatedat:
https://developer.apple.com/account/ios/identifier/bundle
ThissteprequiresanactiveAppleDeveloperProgramaccount.
5. FillouttheNameandBundleIDfieldsforyourprojectandpress
theContinuebutton.
6. Next,we'lllogintotheiTunesConnectsite,locatedathttps://itunesconnect.a
pple.com.IntheMyAppssection,pressthePlus(+)buttonandselectNew
App.
7. You'llneedtofillouttheNameandLanguageinthefollowingdialog,then
selecttheBundleIDmatchingtheoneyoucreatedearlierintherecipe.
Also,addauniqueappreferencefortheSKUandpresstheCreatebutton.
8. Let'sreturntoXcodeandcreatethe.ipafile.SelectGenericiOSDevicefor
theactivescheme,andcreatethefileviathemenus(Product|Archive),
whichwillopentheArchiveslist.Finally,pressUploadtoAppStore.
9. SelectyourProvisioningTeam,thenpressChoose.
10. Oncethearchivehasbeencreated,presstheUploadbutton.Oncethebuild
hasbeenprocessed,you'llreceiveanemailfromiTunesConnect.
11. Oncetheappisprocessed,returntoiTunesConnect.UndertheAppStore
section,openAppInformationandselectthecategorythatyourappfits
into.
12. Openthe1.0PrepareforSubmissionsectionunderiOSAPP.Filloutallthe
requiredfields,includingAppScreenshots,Description,Keywords,and
SupportURL.
13. Next,undertheBuildsection,selectthe.ipawebuiltinstep8.
14. Finally,fillouttheCopyrightandAppReviewInformationsections,then
clicktheSubmitforReviewbutton.
Howitworks...
Inthisrecipe,wecoveredthestandardprocessforpublishingiOSappstothe
AppStore.TherearenoReactNative-specificstepsweneededtofollowinthis
case,sincethefinalproduct(the.ipafile)containsallofthecodeneededtorun
theReactNativepackager,whichwillinturnbuildthemain.jsbundlefileinrelease
mode.
Deployingproductionbuildsto
GooglePlayStore
Thisrecipewillwalkthroughtheprocessofpreparingaproductionbuildofour
appandsubmittingittotheGooglePlayStore.Asinthelastrecipe,we'llstop
rightbeforeactuallysubmittingtotheAppStore,sincethisisonlyanexample
ReactNativeapp,buttherestofthisprocessisalsostraightforward.
Gettingready
Forthisrecipe,wewillbeusingthesamesimpleReactNativeappwe'veused
throughoutthischapter,TestDeployApp.YouwillneedtohaveaGooglePlay
Developeraccountinordertosubmitanapptothestore,andyou'llalsoneedto
havealltheiconsandscreenshotsreadyforthePlayStoreifyouwant
toactuallypublishyourapp.
Howtodoit...
1. Let'sstartbyopeningtheReactNativeprojectinAndroidStudio.Thefirst
stepisbuildingthe.apkfile.Asmentionedearlierinthischapter,the
processofcreatingaproductionAndroidappfromaReactNativeprojectis
involvedandpronetochange.VisittheReactNativeDocumentationfor
creatingthe.apkathttps://facebook.github.io/react-native/docs/signed-apk-android
.html.
2. Next,let'sopentheGooglePlayDeveloperConsoleinawebbrowser,
locatedathttps://play.google.com/apps/publish/.
3. Let'skickofftheprocessbyclickingAddnewapplication.Filloutthe
Titlefield,andclicktheUploadAPKbutton,asfollows:
4. You'llseetheAPKsectionofthePublishscreennext.ClickUploadyour
firstAPKtoProduction,thendraganddrop(orselect)your.apkfile.
5. Aseriesofself-explanatorymodalswillfollow.GothroughtheStore
Listing,ContentRating,Pricing,andDistributionsectionsandfilloutallof
theinformationaccordingly.
6. Onceyouhavesatisfiedalltherequirements,pressthePublishAppbutton.
Howitworks...
Inthisrecipe,wecoveredtheprocessforpublishingAndroidappstotheGoogle
PlayStore.Byfollowingthedirectionslinkedtoinstep2,yourReactNativeapp
willhavebeenthroughtheGradleassembleReleaseprocess.Theassembleprocess
runsthepackagertocreatetheJavaScriptbundlefile,compiletheJavaclasses,
packagethemtogetherwiththeappropriateresources,andfinallyallowyouto
signtheappintoan.apk.
DeployingOver-The-Airupdates
OneusefulsideeffectofourReactNativeappbeingwritteninJavaScriptisthat
thecodeisloadedatruntime,whichissimilartohowCordovahybrid
applicationswork.Wecanleveragethisfunctionalitytopushupdatestoour
applicationusingOver-The-Air(OTA).Thisallowsforaddingfeaturesandbug
fixeswithouthavingtogothroughtheAppStoreapprovalprocess.Theonly
limitationtoOTAupdatesforReactNativeisthatwecannotpushcompiled
(Objective-CorJava)code,whichmeanstheupdatecodemustbeinthe
JavaScriptlayeronly.Thereareafewpopularservicesthatprovidecloud-based
OTAappupdates.WewillbehighlightingCodePush,aservicebyMicrosoft.
ThisrecipewillcoversettingupandpushingupdatesusingCodePushforourReact
NativeapponbothiOSandAndroid.
Gettingready
Forthisrecipe,wewillbeusingthesamesimpleReactNativeappwe'veused
throughoutthischapter,TestDeployApp.We'llbedeployingtheappstophysical
devicesrunninginproduction/releasemode,whichwillallowtheapptoreceive
updatesfromtheCodePushservers.
Howtodoit...
1. InordertouseCodePush,wewillneedtoinstalltheCodePushCLIand
createafreeaccount.ThiscanbedoneinaTerminalbyrunningthe
followingtwocommands:
npminstall-gcode-push-cli
code-pushregister
2. ThenextstepistoregisterourappwithCodePush.Makeanoteofthe
deploymentkeysfortheappprovidedbytheoutputfromrunningcode-push
register.Wewillbeusingthestagingkeyforthisrecipe.The
documentationsuggestsaddingoneappperplatform,withan-IOSor-
Androidsuffixforeach.ToaddtheapptoCodePush,usethiscommand:
code-pushappaddTestDeployApp-IOS
code-pushappaddTestDeployApp-Android
3. We'realsogoingtoneedtheReactNativeCodePushmoduleinstalledinthe
ReactNativeprojectdirectory.Thiscanbedonewithnpm,asfollows:
npminstall--savereact-native-code-push
Or,wecanuseyarn,forexample:
yarnaddreact-native-code-push
4. ThenextstepislinkingtheCodePushnativemoduleswithourproject.
WhenpromptedforyourdeploymentkeyforAndroidandiOS,usethe
stagingkeydiscussedinstep2.Linkingthenativemodulescanbedone
withthefollowingcommand:
react-nativelinkreact-native-code-push
5. Next,weneedtosetourReactNativeappuptouseCodePush.Inside
ofindex.js,we'llneedtoaddthreethings:theCodePushimport,anoptions
object,andacalltotheimportedcodePushmodulewhenregisteringtheapp
viaAppRegistry.registerComponent.Setuptheappasfollows:
import{AppRegistry}from'react-native';
importAppfrom'./App';
importcodePushfrom'react-native-code-push';
constcodePushOptions={
updateDialog:true
}
AppRegistry.registerComponent('TestDeployApp',
()=>codePush(codePushOptions)(App)
)
6. TotestoutourchangesintheiOSapp,let'sdeploytoouriOSdevice.Open
theReactNativeprojectinXcode,changeyourscheme'sBuild
Configuration(Product|Scheme|EditScheme...)toRelease,thenpress
Run,asfollows:
7. Next,makesomesortofarbitrarychangetotheReactNativecodeinthe
app,thenintheTerminal,runthefollowingcommandtoupdatetheapp
withthenewcode:
code-pushrelease-reactTestDeployAppios-m--description"UpdatingusingCodePush"
8. Next,closeandreopentheapponyouriOSdevice.Youshouldseethe
followingprompt:
9. Aftercontinuingpasttheprompt,theappwillupdateitselftothelatest
version!
10. Let'salsotestthefeatureonAndroid.You'llneedtohavemadeyour
Androidappintoa.apkfilebyfollowingthestepsoutlinedintheReact
Nativedocumentationathttps://facebook.github.io/react-native/docs/signed-apk-
android.html.
11. WithyourAndroiddevicepluggedintoyourdevelopmentmachine,runthe
followingcommandintheTerminalfromtheandroid/directory:
adbinstall
app/build/outputs/apk/app-release.apk
12. Next,makechangetotheReactNativeJavaScriptcode.Aslongasnew
codeisadded,wecanusethatchangedcodetoupdatetheapp.Then,run
thefollowingcommandintheTerminal:
code-pushrelease-reactTestDeployAppandroid-m--description"UpdatingusingCodePush"
13. Onceagain,closeandreopenyourapponyourAndroiddevicetogetthe
followingprompt:
14. Afterproceedingpasttheprompt,theappwillupdateitselftothelatest
version.
Howitworks...
CodePush(aswellasothercloud-hostedOTAupdateplatforms)worksbyusing
thesametechniquethathasexistedinReactNativesinceitsinception.React
NativeloadsaJavaScriptbundlewhentheappisinitialized.During
development,thisbundleisloadedfromlocalhost:3000.Oncewe'vedeployedan
app,however,itwilllookforafilenamedmain.jsbundlethathasbeenincludedin
thefinalproduct.ByaddingthecalltocodePushinregisterComponentinstep5,the
appwillcheckinwiththeCodePushAPItoseeifthereisanupdate.Ifthereisa
newupdate,itwillprompttheuseraboutit.Acceptingthepromptdownloads
thenewjsbundlefileandrestartstheapp,causingthecodetobeupdated.
OptimizingReactNativeappsize
Beforedeployingourapptoproduction,it'salwaysagoodideatoshrinktheapp
bundlesizetoassmallafileaspossible,andthereareseveraltechniqueswecan
leveragetodoso.Thesecaninvolvesupportingfewerdevicesorcompressing
includedassets.
Thisrecipewillcoverafewtechniquesforlimitingproductionpackagefilesizes
inbothiOSandAndroidReactNativeapps.
Gettingready
Forthisrecipe,wewillbeusingthesamesimpleReactNativeappwe'veused
throughoutthischapter,TestDeployApp.You'llalsoneedtohavecodesigning
workingforiOS,andtheabilitytocreate.apkfilesascoveredinprevious
recipes.
Howtodoit...
1. Wewillstartoffwithsomesimpleoptimizationsperformedonourbundled
assets,whichoftenincludesimagesandexternalfonts:
ForPNGandJPEGcompression,youcanuseaservicesuchashttp://w
ww.tinypng.comtoreducethefilesizewithlittletonoreductioninimage
quality.
Ifyouusethereact-native-vector-iconslibrary,youwillnoticethatit
bundleseightdifferentfonticonsets.It'srecommendedthatyou
removeanyoftheiconfontlibrariesthatarenotbeingusedbyyour
app.
SVGfilescanalsobecompressedandoptimized.Oneserviceforthis
purposeishttp://compressor.io.
Anyaudioassetspackagedwithyourappshouldbeusingafileformat
thatcanleveragehighqualitycompression,suchasMP3orAAC.
2. ForiOS,there'snotmuchthatcanbedonetofurtherreducefilesize
beyondthesettingsthatareenabledbydefaultonthereleasescheme.These
includeenablingBitcodeforappthinningandsettingthecompiler
optimizationtoFastest,Smallest[-Os].
3. ForAndroid,therearetwothingsyoucandothatcouldimprovefilesize:
InAndroidStudio,openandroid/app/build.gradleandlocatethe
followinglines,thenupdatetheirvaluestothefollowing:
defenableSeparateBuildPerCPUArchitecture=true
defenableProguardInReleaseBuilds=true
4. IfyouplantoonlytargetARM-basedAndroiddevices,wecanpreventit
frombuildingforx86altogether.Inthebuild.gradlefile,locatethesplitsabi
objectandaddthefollowinglinetonotincludex86support:
include"armeabi-v7a"
YoucanreadmoreaboutABImanagementintheAndroiddocsat:
https://developer.android.com/ndk/guides/abis
Howitworks...
Inthisrecipe,wecoveredtechniquesthatcanbeusedtoreduceappfilesize.
ThesmallertheJavaScriptbundleis,thefastertheJavaScriptinterpreterwillbe
abletoparsethecode,translatingintofasterapploadtimes,andquickerOTA
updates.Thesmallerwecankeepour.ipaand.apkfiles,thefasterouruserswill
beabletodownloadtheapp.
OptimizingthePerformanceofYour
App
Inthischapter,wewillcoverthefollowingrecipes:
OptimizingourJavaScriptcode
OptimizingtheperformanceofcustomUIcomponents
Keepinganimationsrunningat60FPS
GettingthemostoutofListView
Boostingtheperformanceofourapp
OptimizingtheperformanceofnativeiOSmodules
OptimizingtheperformanceofnativeAndroidmodules
OptimizingtheperformanceofnativeiOSUIcomponents
OptimizingtheperformanceofnativeAndroidUIcomponents
Introduction
Performanceisakeyrequirementofalmosteverysinglepieceoftechnologyin
softwaredevelopment.ReactNativewasintroducedtosolvetheissueofpoor
performancethatexistedinhybridappsthatwrapwebapplicationsinanative
container.ReactNativehasanarchitecturethatlendsitselftobothflexibilityand
excellentperformance.
WhenconsideringtheperformanceofaReactNativeapp,itisimportanttothink
aboutthebigpictureofhowReactNativeworks.Therearethreemajorpartstoa
ReactNativeapp,andtheirrelativeperformanceisdepictedinthefollowing
diagram:
Therecipesinthischapterfocusonusinglower-levelfunctionsthattakeupless
memoryandhavefeweroperations,thusloweringthetimeittakesforataskto
complete.
OptimizingourJavaScriptcode
It'ssafetosaythatyourReactNativeappswillprobablybewrittenmostlyin
JavaScript.TheremaybesomenativemodulesandcustomUIcomponents,but
forthemostpart,alloftheviewsandbusinesslogicwilllikelybewritteninJSX
andJavaScript.Andifyou'reusingmodernJavaScriptdevelopmenttechniques,
you'llalsobeusinglanguageconstructsintroducedwithES6,ES7,andbeyond.
ThesemaybeavailablenativelyaspartoftheJavaScriptinterpreterbundled
withReactNative(JavaScriptCore)orpolyfilledbytheBabeltranspiler.Since
JavaScriptprobablyconstitutesthemajorityofanygivenReactNativeapp,this
shouldbethefirstpartweoptimizeinordertosqueezeextraperformanceoutof
theapp.
ThisrecipewillprovidesomehelpfultipsforoptimizingJavaScriptcodeto
makeitasperformantaspossible.
Gettingready
ThisrecipeisnotnecessarilydependentonReactNative,sinceitfocusesonthe
JavaScriptthat'susedtowriteanyReactapp.Someofthesesuggestionsare
micro-optimizationsthatwillprobablyonlyimproveperformanceon
older/slowerdevices.Dependingonwhichdevicesyouintendtosupport,some
tipswillgofurtherthanothers.
Howtodoit...
1. Thefirstoptimizationtolookatisspeedingupiterations.Often,you'll
likelybeusingfunctionsthattakeiteratorfunctionsasarguments(forEach,
filter,andmap).Asaruleofthumb,thesewillbeslowerthandoinga
standardforloop.Ifthesizeofthecollectionyou'reiteratingoverisvery
large,thiscouldmakeadifference.Here'sanexampleofafasterfilter
function:
letmyArray=[1,2,3,4,5,6,7];
letnewArray;
//Slower:
functionfilterFn(element){
returnelement>2;
}
newArray=myArray.filter(filterFn);
//Faster:
functionfilterArray(array){
varlength=array.length,
myNewArray=[],
element,
i;
for(i=0;i<length;i++){
element=array[i];
if(element>2){
myNewArray.push(array[i]);
}
}
returnmyNewArray;
}
newArray=filterArray(myArray);
2. Whenoptimizingiterations,itcanalsobemoreperformanttoensure
thatyoustorethevariablesyouareaccessingontheiteration,somewhere
closeby:
functionfindInArray(propertyerties,appConfig){
for(leti=0;i<propertyerties.length;i++){
if(propertyerties[i].somepropertyerty===
appConfig.userConfig.permissions[0]){
//dosomething
}
}
}
functionfasterFindInArray(propertyerties,appConfig){
letmatchPermission=appConfig.userConfig.permissions[0];
letlength=propertyerties.length;
leti=0;
for(;i<length;i++){
if(propertyerties[i].somepropertyerty===matchPermission){
//dosomething
}
}
}
3. Youcanalsooptimizeyourlogicalexpressions.Keepyourfastestand
closestexecutingstatementsontheleft:
functioncanViewApp(user,isSuperUser){
if(getUserPermissions(user).canView||isSuperUser){
returntrue;
}
}
functioncanViewApp(user,isSuperUser){
if(isSuperUser||getUserPermissions(user).canView){
returntrue;
}
}
4. WhilemodernJavaScript(ES6,ES7,andsoon)constructscanbemore
enjoyabletodevelopwith,someoftheirfeaturesexecutemoreslowlythan
theirES5counterparts.Thesefeaturescanincludeforof,generators,
Object.assign,andothers.Agoodreferenceforperformancecomparisonscan
befoundathttps://kpdecker.github.io/six-speed/.
5. Itcanbehelpfultoavoidtry-catchstatements,sincetheycanaffectthe
optimizationoftheinterpreter(asisthecaseinV8).
6. Arraysshouldhavemembersthatareallofthesametype.Ifyouneedto
haveacollectionwherethetypecanvary,useanobject.
Howitworks...
JavaScriptperformanceisatopicofconstantdebate.Itissometimesdifficultto
keepupwiththelatestinperformancemetrics,sinceGoogle,Apple,Mozilla,
andtheglobalopensourcecommunityisalwayshardatworkimprovingtheir
JavaScriptengines.ForReactNative,wefocusonWebKitJavaScriptCore.
Optimizingtheperformanceof
customUIcomponents
WhilebuildingyourReactNativeapp,it'sasafebetthatyouwillbecreating
customUIcomponents.Thesecomponentscaneitherbecompositionsofseveral
othercomponentsoracomponentthatbuildsontopofanexistingcomponent
andaddsmorefunctionality.Withaddedfunctionality,complexityalso
increases.Thisincreasedcomplexityleadstomoreoperations,andinturn,the
potentialforslowdowns.Fortunately,therearesomewaystomakesurethatour
customUIcomponentsareperformingthebesttheycan.Thisrecipeshows
severaltechniquesforgettingthemostoutofourcomponents.
Gettingready
ThisreciperequiresthatyouhaveaReactNativeappwithsomecustom
components.Astheseperformancesuggestionsmayormaynotprovidevalueto
yourapp,usediscretionwhenyouchoosetoapplythesetoyourcode.
Howtodoit...
1. Thefirstoptimizationweshouldlookatiswhatistrackedinthestateobject
ofagivencomponent.Weshouldmakesurethatalltheobjectswehavein
thestatearebeingused,andthateachcanpotentiallychange,causinga
desiredre-render.Ifanypiecesofdatacanbepassedaspropertys
instead,makesuretheyarepropertys.Storinganobjectmemberasastate
insteadoftheentireobjectcanalsoincreaseperformance.
2. Takealookattherenderfunctionofeachcomponent.Theoverallgoalisto
keepthisfunctionperformingasfastaspossible,sotrytoensurethatno
long-runningprocessesoccurwithinit.Ifyoucan,cachecomputationsand
constantvaluesoutsidetherenderfunctionsothattheyarenotinstantiated
everytime.
3. IfyouhaveconditionalJSXthatmayreturnintherenderfunction,returnas
earlyaspossible.Here'satrivialexample:
//unoptimized
render(){
letoutput;
constisAdminView=this.propertys.isAdminView;
if(isAdminView){
output=(<AdminButton/>);
}else{
output=(
<Viewstyle={styles.button}>
<Text>{this.propertys.buttonLabel}</Text>
</View>
);
}
returnoutput;
}
//optimized
render(){
constisAdminView=this.propertys.isAdminView;
if(isAdminView){
return(<AdminButton/>);
}
return(
<Viewstyle={styles.button}>
<Text>{this.propertys.buttonLabel}</Text>
</View>
);
}
4. Themostimportantoptimizationwecanmakeistoskiptherendermethod
altogetherifitisn'tneeded.Thisisdonebyimplementingthe
shouldComponentUpdatemethodandreturningfalsefromit,makingitapure
component.Here'showwecanmakeacomponentaPureComponent:
importReact,{PureComponent}from'react';
exportdefaultclassButtonextendsPureComponent{
}
Howitworks...
ThemajorityofyourReactNativeappswillconsistofcustomcomponents.
Therewillbeamixofstatefulandstatelesscomponents.Ashighlightedinstep
2,theoverallgoalistorenderourcomponentintheshortestamountoftime.
Anothergaincanbeachievedifacomponentcanbearchitectedtoonlyhaveto
renderthecomponentonceandthenbeleftuntouched,ascoveredinstep4.For
moreinformationonhowpurecomponentsareusedandhowtheycanbe
beneficial,checkouthttps://60devs.com/pure-component-in-react.html.
Seealso
YoucanfindsomemoreinformationaboutReactcomponentperformance
optimizationsintheofficialdocumentationathttps://reactjs.org/docs/optimizing-per
formance.html.
Keepinganimationsrunningat60
FPS
Animportantaspectofanyqualitymobileappisthefluidityoftheuser
interface.Animationsareusedtoprovidearichuserexperience,andanyjankor
jittercannegativelyaffectthis.Animationswilllikelybeusedforallkindsof
interactions,fromchangingbetweenviews,toreactingtoauser'stouch
interactiononacomponent.Thesecondmostimportantfactorforhigh-quality
animationsistomakesurethattheydonotblocktheJavaScriptthread.Tokeep
animationsfluidandnotinterruptUIinteractions,therenderloophastorender
eachframein16.67ms,sothat60FPScanbeachieved.
Inthisrecipe,wewilltakealookatseveraltechniquesforimprovingthe
performanceofanimations.Thesetechniquesfocusinparticularonpreventing
JavaScriptexecutionfrominterruptingthemainthread.
Gettingready
Forthisrecipe,we'llassumethatyouhaveaReactNativeappthathassome
animationsdefined.
Howtodoit...
1. Firstandforemost,whendebugginganimationperformanceinReact
Native,we'llwanttoenabletheperformancemonitor.Todoso,showthe
DevMenu(shakethedeviceorcmd+Dfromthesimulator)andtapShow
PerfMonitor.
TheoutputiniOSwilllooksomethinglikethefollowingscreenshot:
TheoutputinAndroidwilllooksomethinglikethefollowing
screenshot:
2. Ifyouarelookingtoanimateacomponent'stransition(opacity)or
dimensions(width,height),thenmakesuretouseLayoutAnimation.Youcanfind
anexampleofusingLayoutAnimationinChapter6,AddingBasicAnimationsto
YourApp,intheExpandingandcollapsingcontainersrecipe.
IfyouwanttouseLayoutAnimationonAndroid,youneedtoaddthefollowingcodewhenyour
applicationstarts:UIManager.setLayoutAnimationEnabledExperimental
&&UIManager.setLayoutAnimationEnabledExperimental(true).
3. Ifyouneedfinitecontrolovertheanimations,itisrecommendedthatyou
usetheAnimatedlibrarythatcomeswithReactNative.Thislibraryallows
youtooffloadalloftheanimationworkontothenativeUIthread.Todoso,
wehavetoaddtheuseNativeDriverpropertytoourAnimatedcall.Let'stakea
sampleAnimatedexampleandoffloadittothenativethread:
componentWillMount(){
this.setState({
fadeAnimimation:newAnimated.Value(0)
});
}
componentDidMount(){
Animated.timing(this.state.fadeAnimimation,{
toValue:1,
useNativeDriver:true
}).start();
}
Currently,onlyasubsetofthefunctionalityoftheAnimatedlibrarysupportsnative
offloading.PleaserefertotheThere'smore...sectionforacompatibilityguide.
4. Ifyouareunabletooffloadyouranimationworkontothenativethread,
thereisstillasolutionforprovidingasmoothexperience.Wecanusethe
InteractionManagertoexecuteataskaftertheanimationshavecompleted:
componentWillMount(){
this.setState({
isAnimationDone:false
});
}
componentWillUpdate(){
LayoutAnimation.easeInAndOut();
}
componentDidMount(){
InteractionManager.runAfterInteractions(()=>{
this.setState({
isAnimationDone:true
});
})
}
render(){
if(!this.state.isAnimationDone){
returnthis.renderPlaceholder();
}
returnthis.renderMainScene();
}
5. Finally,ifyouarestillsufferingfrompoorperformance,you'llhaveto
eitherrethinkyouranimationstrategyorimplementthepoorlyperforming
viewasacustomUIviewcomponentonthetargetplatform(s).Youwill
havetoimplementbothyourviewandanimationnativelyusingtheiOS
and/orAndroidSDK.InChapter11,AddingNativeFunctionality,we
coveredcreatingcustomUIcomponentsintheRenderingcustomiOSview
componentsandRenderingcustomAndroidviewcomponentsrecipes.
Howitworks
ThetipsinthisrecipefocusonthesimplegoalofpreventingtheJavaScript
threadfromlocking.ThemomentourJavaScriptthreadbeginstodropframes
(lock),welosetheabilitytointeractwithourapplication,evenifit'sfora
fractionofasecond.Itmayseeminconsequential,buttheeffectisfelt
immediatelybyasavvyuser.Thefocusofthetipsinthisrecipeistooffload
animationsontotheGPU.Whentheanimationisrunningonthemainthread
(thenativelayer,renderedbytheGPU),theusercaninteractwiththeappfreely
withoutstuttering,hanging,jank,orjitters.
There'smore...
Here'saquickreferenceforwhereuseNativeDriverisusable:
Function iOS Android
style,value,propertys
decay
timing
spring
add
multiply
modulo
diffClamp
interpoloate
event
division
transform
GettingthemostoutofListView
ReactNativeprovidesaprettyperformantlistcomponentoutofthebox.Itis
extremelyflexible,supportsrenderingalmostanycomponentyoucanimagine
insideofit,andrendersthemratherquickly.Ifyou'dliketoreadsomemore
examplesofhowtoworkwithListView,thereareacoupleofrecipesinthisbook,
includingrecipesinChapter2,CreatingaSimpleReactNativeApp,thatuseit.
TheReactNativeListViewisbuiltontopofScrollViewtoachievetheflexibilityof
renderingvariable-heightrowswithanyviewcomponent.
ThemajorperformanceandresourcedrawbackoftheListViewcomponentoccurs
whenyouareworkingwithanextremelylargelist.Astheuserscrollsthrough
thelist,thenextpageofrowsisrenderedatthebottom.Theinvisiblerowsatthe
topcanbesettoberemovedfromtherendertree,whichwewillcovershortly.
However,thereferencestotherowsarestillinmemoryaslongasthe
componentismounted.Naturally,asourcomponentusesuptheavailable
memory,therewillbelessroomforquicklyaccessiblestoragefortheupcoming
components.Thisrecipewillcoverdealingwithsomeofthesepotential
performanceandmemoryresourceissues.
Gettingready
Forthisrecipe,weassumethatyouhaveaReactNativeappthatismakinguse
ofaListView,preferablywithalargedataset.
Howtodoit...
1. Let'sstartwithsomeoptimizationswecanmaketoourvanillaListView
component.IfwesettheinitialListSizepropertyto1,wecanspeedupthe
initialrendering.
2. Next,wecanbumpupthepageSizeifthecomponentbeingrenderedineach
rowisnotcomplex.
3. AnotheroptimizationissettingthescrollRenderAheadDistancetoacomfortable
value.Ifyoucanexpectuserstorarelyscrollpasttheinitialviewport,or
thatthey'relikelytoscrollslowly,thenyoucanlowerthevalue.This
preventstheListViewfromrenderingtoomanyrowsinadvance.
4. Finally,thelastoptimizationwecanmakeuseofistheremoveClippedSubviews
property.However,theofficialdocumentationstatesthefollowing:
"Thefeaturemayhavebugs(missingcontent)insomecircumstances-useatyourownrisk."
5. Combiningsteps1tostep4canbeseeninthefollowingexamplecode:
renderRow(row){
return(
<Viewstyle={{height:44,overflow:'hidden'}}>
<Text>Item{row.index}</Text>
</View>
)
}
render(){
return(
<Viewstyle={{flex:1}}>
<ListView
dataSource={this.state.dataSource}
renderRow={this.renderRow}
pageSize={10}
initialListSize={1}
pageSize={10}
scrollAheadDistance={200}
/>
</View>
)
}
Howitworks...
Aswithdevelopinganyapp,themoreflexibleandcomplexsomethingis,the
sloweritperforms.ListViewisanexcellentexampleofthisconcept.Itis
extremelyflexible,sinceitcanrenderanyViewinarow,butitcanquicklybring
yourapplicationtoahaltifnotusedcarefully.Theresultoftheoptimizations
definedinstep1tostep4willvaryacrossdifferentsituationsbasedonwhatyou
arerenderingandthedatastructurethatisbeingusedbytheListView.Youshould
experimentwiththesevaluesuntilyoufindagoodbalance.Asalastresort,if
youarestillunabletoachievetherequiredperformancebenchmark,youcan
lookatsomeofthecommunitymodulesthatprovidenewListView
implementationsoralternatives.
Seealso
Thefollowingisalistofsomeofthethird-partyListViewimplementationsthat
promiseincreasedperformance:
recyclerlistview:ThislibraryisthemostrobustalternativetoListView,
boastingalonglistofimprovementsandfeatures,includingsupport
forstaggeredgridlayouts,horizontalmode,andfootersupport.The
repositoryislocatedathttps://github.com/Flipkart/recyclerlistview.
react-native-sglistview:ThistakesremoveClippedSubviewstothenextlevelby
flushingthememorywhentheoffscreenrowsareremovedfromtherender
tree.Therepositoryislocatedathttps://github.com/sghiassy/react-native-sglistv
iew.
Boostingtheperformanceofourapp
ThereasonforReactNative'sexistenceisbuildingnativeappswithJavaScript.
ThisisdifferentthansimilarframeworkssuchasIonicorCordovahybridapp,
whichwrapawebapplicationwritteninJavaScriptandattempttoemulate
nativeappbehavior.ThosewebapplicationsonlyhaveaccesstonativeAPIsfor
performingprocessing,butcannotrendernativeviewsinsidetheirapps.Thisis
onemajorbenefittoReactNativeapps,thusmakingtheminherentlyfasterthan
hybridapps.Sinceit'ssomuchmoreperformantoutofthebox,wegenerallydo
nothavetoworryaboutoverallperformanceasmuchaswewouldwithahybrid
webapp.Still,withalittleextraeffort,aslightimprovementinperformance
mightbeachievable.Thisrecipewillprovidesomequickwinsthatwecanuse
tobuildfasterReactNativeapps.
Howtodoit...
1. Thesimplestoptimizationwecanmakeistonotoutputanystatementsto
theconsole.Performingaconsole.logstatementisnotastrivialataskas
you'dimaginefortheframework,soit'srecommendedtoremoveall
consolestatementswhenyouarereadytobundleyourfinalapp.
2. Ifyouusealotofconsolestatementsduringdevelopment,youcanhave
Babelautomaticallyremovethemwhencreatingthebundlebyusing
thetransform-remove-consoleplugin.Thiscanbeinstalledintotheprojectvia
theTerminalusingyarn:
yarnaddbabel-plugin-transform-remove-console
3. Alternatively,youcanusenpm:
npminstallbabel-plugin-transform-remove-console--save
Withthepackageinstalled,youcanaddittotheprojectbyadding
a.babelrcfilecontainingthefollowingcode:
{
"presets":["react-native"],
"env":{
"production":{
"plugins":["transform-remove-console"]
}
}
}
4. Next,makesurethatwhenyou'reanalyzingyourperformance,yourappis
runninginproductionmode,preferablyonadevice.Ifyouarecurious
abouthowtodothis,youcanrefertotheDeployingtestbuildsto
HockeyApprecipeinChapter13,DeployingOurApp.
5. Sometimes,whenyouareanimatingthepositionorlayoutofaView,you
maynoticeperformancedipsintheUIthread.Youcanmitigatethisby
settingtheshouldRasterizeIOSandrenderToHardwareTextureAndroidpropertiesto
trueforiOSandAndroidplatforms.Bemindfulthatthismayincrease
memoryusagesignificantly,sobesuretotesttheperformanceafterthese
changesaswell.
6. Ifyoufindthatyouneedtotransitionviewsusinganavigationstatechange
whilealsoperformingsynchronous,potentiallylong-runningprocesses,it
canbecomeaperformancebottleneck.Thiscommonlyoccurswhen
buildingaDataSourceforaListVieworwhentransformingdatatopowerthe
upcomingview.Youshouldexperimentwithprocessingonlyaninitial
subsetofthedata,enoughtorendertheUIquicklyenough.Oncethe
animationcompletesbetweenpagetransitions,youcanuseInteractionManager
toloadtherestofthedata.YoucanrefertotheKeepinganimationsrunning
at60FPSrecipeformoreinformationonhowtouseInteractionManager.
7. Finally,ifyouhaveidentifiedaparticularcomponentortaskthatisslowing
downyourapp,andcannotfindaviablesolution,thenyoushouldconsider
movingittothenativethreadbycreatinganativemoduleornativeUI
componenttoimplementthispieceoffunctionality.
Howitworks...
Thisrecipecoverssomehigher-levelandbroader-scopedtipsforallReact
Nativeapps.Themostsignificantperformancegainsyouwilllikelyseefrom
thesetipsarefrommovingacomponenttothenativelayer,ascoveredinstep7.
Optimizingtheperformanceofnative
iOSmodules
Often,whenbuildingaReactNativeapp,youwillneedtoworkwithnative
AndroidandiOScode.Youmayhavebuiltthesenativemodulestoexposesome
extrafunctionalityprovidedbyanativeAPI,orperhapsyourappneededto
performanintensivebackgroundtask.
Aswastouchedonearlier,workinginthenativelayerreallyallowsyoutomake
useofadevice'sfullcapacity.However,itdoesn'tmeanthatthecodewewrite
willautomaticallybethefastestitcouldbe.There'salwaysroomtooptimizeand
achieveperformancegains.
Inthisrecipe,wewillprovidesometipsonhowtomakeyourObjective-Ccode
runabitfasterusingtheiOSSDKs.WewillalsoconsiderhowReactNativeand
theReactNativebridge,whichisusedtocommunicatebetweentheJavaScript
andthenativelayers,fitintothebiggerpicture.
Gettingready
Forthisrecipe,youshouldhaveaReactNativeappthatusesnativemodulesthat
havebeencreatedforiOS.Ifyouneedhelpwithwritingnativemodules,takea
lookattheExposingcustomiOSmodulesrecipeinChapter11,AddingNative
Functionality.
Howtodoit...
1. Firstandforemost,whenworkingwithnativemodules,wehavetobe
mindfulofthedatagoingthroughtheReactNativebridge.Keepingthe
dataincross-bridgeeventsandcallbackstoaminimumisalwaysthegoal,
sincethedataserializationbetweenObjective-CandJavaScriptisvery
slow.
2. Ifyouneedtokeepdatacachedinmemoryforconsumptionbythenative
module,keepitstoredinalocalpropertyorfieldvariable.Nativemodules
aresingletons.Dothisinsteadofreturningalargeobjecttostoreinthe
ReactNativecomponent.
3. Sometimes,wehavetoleverageclassesthatarelargebecausetheyare
robustintheirfeatureset.FortheObjective-CandiOSsideofthings,
insteadofinstantiatingsomethinglikeNSDateFormatterinyourmethodeach
timethatyouexposethefeatureviaRCT_EXPORT_METHOD,storethereferenceof
thisclassasapropertyertyoraninstancevariable.
4. Furthermore,nativemethodssuchasNSDateFormatterareoftenextremely
heavy,soavoidingthemisadvisablewherepossible.Forinstance,ifyour
applicationcandealwithjustUNIXtimestamps,thenyoucaneasilygetan
NSDateobjectfromatimestampwiththefollowingfunction:
-(NSDate*)dateFromUnixTimestamp:(NSTimeInterval)timestamp{
return[NSDatedateWithTimeIntervalSince1970:timestamp];
}
5. Themostsignificantperformanceoptimizationyoucanmake,ifthe
situationpresentsitself,isspawningasynchronousbackgroundthreadsto
handleintensiveprocessing.ReactNativefitsthismodelwell,sinceituses
anasynchronousmessaging/eventsystemtocommunicatebetweenthe
JavaScriptandnativethreads.Whenyourbackgroundprocessiscomplete,
youcaneitherinvokeacallback/promiseorfireaneventfortheJavaScript
threadtopickup.Tolearnhowtocreateandleveragebackground
processesinReactNativeiOSnativemodules,checkouttheBackground
processingoniOSrecipeinChapter11,AddingNativeFunctionality.
Howitworks...
Objective-Ccodeexecutesveryquickly–almostasquicklyasvanillaC.
Therefore,theoptimizationsweperformdonothavemuchtodowithexecuting
tasksbutratherwithhowthingsareinstantiatedandbynotblockingnative
threads.Thebiggestperformanceboostyou'llseeisbypropertyusingthe
GrandCentralDispatch(GCD)tospawnbackgroundprocesses,asdescribed
instep5.
Optimizingtheperformanceofnative
Androidmodules
WhiledevelopingyourReactNativeapplication,youmayfindyourselfwriting
nativeAndroidmodulestoeithercreatecross-platformfeaturesonbothiOSand
AndroidortomakeuseofnativeAPIsthathavenotbeenwrappedasfirst-party
modulesforAndroidbutthatdoexistoniOS.Hopefully,youfoundsomeuseful
adviceonworkingwithnativemodulesinChapter11,AddingNative
Functionality.
Inthisrecipe,wewillcoverseveraltechniquesforspeedingupourReactNative
Androidnativemodules.Manyofthesetechniquesarelimitedtogeneral
developmentonAndroid,andafewwilladdresscommunicatingwiththeReact
NativeJavaScriptlayer.
Gettingready
Forthisrecipe,youshouldhaveaReactNativeappthatmakesuseofthenative
modulesyoucreatedforAndroid.Ifyouneedhelpwithwritingnativemodules,
pleasetakealookattheExposingcustomAndroidmodulesrecipeinChapter
11,AddingNativeFunctionality.
Howtodoit...
1. Firstandforemost,justaswithiOSnativemodules,you'llwanttolimitthe
amountofdatacrossingtheReactNativebridge.Keepingthedatathat'sin
eventsandcallbackstoaminimumwillhelptoavoidslowdownscausedby
theserializationbetweenJavaandJavaScript.Also,aswithiOS,trytokeep
datacachedinmemorytobeusedbythenativemodule;keepitstoredina
privatefield.Nativemodulesaresingletons.Thisshouldbeleveraged
insteadofreturningalargeobjecttostoreintheReactNativecomponent.
2. WhenwritingJavacodeforAndroid,youshoulddoyourbesttoavoid
creatingshort-termobjects.Ifyoucan,useprimitives,especiallyfor
datasetssuchasarrays.
3. Itisbettertoreuseobjectsinsteadofrelyingonthegarbagecollectorto
pickupanunusedreferenceandinstantiateanewobject.
4. TheAndroidSDKprovidesamemory-efficientdatastructureforreplacing
theuseofaMap,whichmapsintegerstoobjects,calledSparseArray.Usingit
canreducememoryusageandimproveperformance.Here'sanexample:
SparseArray<SomeType>map=newSparseArray<SomeType>();
map.put(1,myObjectInstance);
ThereisalsoSparseIntArray,whichmapsintegerstointegers,andSparseBooleanArray,whichmaps
integerstoBooleanvalues.
5. WhileitmaysoundcounterintuitivetodevelopersusedtoOOP
developmentinJava,avoidingtheuseofgettersandsettersbyaccessing
theinstancefielddirectlycanalsoimproveperformance.
6. Ifyou'reeverworkingwithStringconcatenation,makeuseofStringBuilder.
7. Lastly,themostsignificantperformanceoptimizationyoucanmake,if
possible,isspawningasynchronousbackgroundthreadstoperformheavy
computationsbyleveragingReactNative'sasynchronousmessaging/event
systemtocommunicatebetweentheJavaScriptandnativethreads.When
yourbackgroundprocessiscomplete,youcaneitherinvokea
callback/PromiseorfireaneventfortheJavaScriptthreadtopickup.To
learnhowtocreatebackgroundprocessesinReactNativeAndroidnative
modules,pleasereadtheBackgroundprocessingonAndroidrecipeinChapte
r11,AddingNativeFunctionality.
Howitworks...
Themajorityofthetipsinthisreciperevolvearoundefficientmemory
management.TheAndroidOSusesatraditional-stylegarbagecollectorsimilar
tothedesktopJavaVM.Whenthegarbagecollectorkicksin,itcantake
anywherebetween100-200mstofreememory.Steps3-6allprovidesuggestions
thatreducetheapp'smemoryusage.
Optimizingtheperformanceofnative
iOSUIcomponents
ReactNativeprovidesuswithanexcellentfoundationtobuildalmostanykind
ofuserinterfaceusingbuilt-incomponentsandstyling.Componentsbuiltin
Objective-CusingtheiOSSDK,OpenGL,orsomeotherdrawinglibrarywill
generallyperformbetterthancomposingtheprebuiltcomponentsusingJSX.
Whenusingthesenativeviewcomponents,therearesomeusecasesthatmay
haveanegativeimpactonappperformance.
ThisrecipewillfocusongettingthemostoutoftheiOSUIKitSDKwhen
renderingcustomviews.Ourgoalistorendereverythingasquicklyaspossible
forourapplicationtorunat60FPS.
Gettingready
Forthisrecipe,youshouldhaveaReactNativeappthatrenderscustomnative
UIcomponentsyouhavewrittenforiOS.IfyouneedhelpwithwrappingUI
componentsinReactNative,pleasetakealookattheExposingcustomiOSview
componentsrecipeinChapter11,AddingNativeFunctionality.
Howtodoit...
1. Asmentionedpreviously,onlypassdataacrosstheReactNativebridge
whenitisunavoidabletodootherwise,sincedataserializationbetween
Objective-CandJavaScripttypesisslow.
2. Ifthereisdatathatyouneedtostoreforreferencingsometimeinthenear
future,it'sbettertostoreitinthenativeclassthatyouinitialized.
Dependingonyourapplication,youcaneitherstoreitasapropertyertyon
theViewManager,asingletonthatservesinstancesoftheView,orapropertyerty
ontheViewitself.
3. IfyourviewcomponentinvolvesrenderingmultipleUIViewinstancesas
childrenofaparentUIViewcontainer,makesurealltheinstanceshavethe
opaquepropertyertysettotrue.
4. Ifyouarerenderinganimageinsideyourviewcomponent(notusingthe
ReactNativeImagecomponent),thensettingyourimagetobethesame
dimensionastheUIImageViewcomponentcanhelpperformance.Scaling,and
otherimagetransformations,areheavyoperationsthatcanimpactonframe
rate.
5. OneofthemostimpactfultweaksinwritingiOSviewcomponentsis
avoidingoffscreenrendering.AvoiddoingthefollowingwithSDK
functionalityifpossible:
UsingclassesthatstartwiththeCoreGraphics(CG)library
OverridingthedrawRectimplementationofUIView
SettingshouldRasterize=YES,orusingsetMasksToBoundsorsetShadowonyour
UIViewinstance'slayerproperty
CustomdrawingsusingCGContext
6. Ifyouneedtoaddashadowtoyourview,makesuretosettheshadowPathto
preventoffscreenrendering.Here'sanexampleofhowtheinitializationand
shadowdefinitionshouldlook:
RCT_EXPORT_MODULE()
-(UIView*)view{
UIView*view=[[UIViewalloc]init];
view.layer.masksToBounds=NO;
view.layer.shadowColor=[UIColorblackColor].CGColor;
view.layer.shadowOffset=CGSizeMake(0.0f,5.0f);
view.layer.shadowOpacity=0.5f;
view.layer.shadowPath=[[UIBezierPathbezierPathWithRect:view.bounds]CGPath];
returnview;
}
Howitworks...
ThisrecipefocusedonsomehelpfultipsthatallowtheGPUtodoasmuchof
theworkasitcan.ThesecondpartdiscussedhowtokeeptheloadontheGPU
aslowaspossible.Enforcingtheopaquepropertyinstep3tellstheGPUnotto
worryaboutcheckingthevisibilityofothercomponentssothatitcancalculate
transparency.Steps5andstep6preventoffscreenrendering.Offscreenrendering
generatesbitmapimagesusingtheCPU(whichisaslowprocess)and,more
importantly,itkeepstheGPUfromrenderingtheviewuntiltheimageshave
beengenerated.
Optimizingtheperformanceofnative
AndroidUIcomponents
Overthelastfewyears,AndroidnativeUIperformancehasimproved
significantly.Thisisprimarilyduetoitsabilitytorendercomponentsand
layoutsusingGPUhardwareacceleration.InyourReactNativeapp,youmay
findyourselfusingcustomviewcomponents,especiallyifyouwanttousea
built-inAndroidfeaturethathasnotyetbeenwrappedasaReactNative
component.EventhoughtheAndroidplatformhasmadeaconsciouseffortto
increasetheperformanceofitsUI,thewaycomponentsarerenderedcanquickly
negateallofthesebenefits.
Inthisrecipe,we'lldiscussafewwaystogetthebestperformanceoutofour
customAndroidviewcomponents.
Gettingready
Forthisrecipe,youshouldhaveaReactNativeapplicationthatrenderscustom
nativeUIcomponentsyouhavewrittenforAndroid.Ifyouneedhelpwith
wrappingUIcomponentsinReactNative,checkouttheExposingcustom
AndroidviewcomponentsrecipeinChapter11,AddingNativeFunctionality.
Howtodoit...
1. Asstatedpreviously,onlycrosstheReactNativebridgewithdatawhen
necessary.Keepthedataineventsandcallbackstoaminimumasthedata
serializationbetweenJavaandJavaScriptisslow.
2. Ifthereisdatathatyouneedtostoreforreferencingsometimeinthenear
future,it'sbettertostoreitinthenativeclassthatyou'veinitialized.
Dependingonyourapplication,youcaneitherstoreitasapropertyonthe
SimpleViewManager,asingletonthatservesinstancesoftheView,oraproperty
ontheViewitself.
3. Whenbuildingoutviews,considerthatcomponentsoftenconsistofother
childcomponents.Thesecomponentsareheldinahierarchyoflayouts.
Over-nestinglayoutscanbecomeaveryexpensiveoperation.Ifyouare
usingmulti-levelnestedLinearLayoutinstances,trytoreplacethemwitha
singleRelativeLayout.
4. YoucananalyzetheefficiencyofyourlayoutusingtheHierarchyViewer
toolthat'sbundledinsidetheAndroidDeviceMonitor.IntheMonitor,
clickWindow|OpenPerspective...|HierarchyViewandselectOK.
5. Ifyouareperformingrepeatedanimationsonyourcustomviewnativelyin
Java(notusingtheReactNativeAnimatedAPI),thenyoucanleverage
hardwarelayerstoimproveperformance.SimplyaddawithLayermethodcall
toyouranimatecall.Forexample:
myView.animate()
.alpha(0.0f)
.withLayer()
.start();
Howitworks...
Unfortunately,therearen'tthatmanyoptimizationsyoucanperformwhenit
comestorenderingAndroidUIcomponents.Theygenerallyrevolvearoundnot
over-nestinglayouts,sincethisincreasescomplexitybyordersofmagnitude.
Whenyouhavelayoutperformanceissues,theappismostlikelysufferingfrom
overusingtheGPU,oroverdrawing.OverdrawingoccurswhentheGPUrenders
anewviewoveranexistingviewthatisalreadyrendered.YoucanenableGPU
OverdrawDebuggingintheAndroidDeveloperSettingsmenu.Theorderof
severityofoverdrawingisNoColor->Blue->Green->LightRed->Dark
Red.
Instep5,weprovidedaquicktipforimprovingtheperformanceofanimations.
Thisisparticularlytrueforrepeatedanimations,sinceitcachestheanimation
outputontheGPUandreplaysit.
OtherBooksYouMayEnjoy
Ifyouenjoyedthisbook,youmaybeinterestedintheseotherbooksbyPackt:
Hands-OnDesignPatternswithReactNative
MateuszGrzesiukiewicz
ISBN:9781788994460
ExplorethedesignPatternsinReactNative
LearnthebestpracticesforReactNativedevelopment
ExplorecommonReactpatternsthatarehighlyusedwithinReactNative
development
Learntodecouplecomponentsandusedependencyinjectioninyour
applications
Explorethebestwaysoffetchingdatafromthebackendsystems
Learnthestylingpatternsandhowtoimplementcustommobiledesigns
Explorethebestwaystoorganizeyourapplicationcodeinbigcodebases
ReactandReactNative-SecondEdition
AdamBoduch
ISBN:9781789346794
LearnwhathaschangedinReact16andhowyoustandtobenefit
CraftreusablecomponentsusingtheReactvirtualDOM
Learnhowtousethenewcreate-react-native-appcommandlinetool
AugmentReactcomponentswithGraphQLfordatausingRelay
HandlestateforarchitecturalpatternsusingFlux
BuildanapplicationforwebUIsusingRelay
Leaveareview-letotherreaders
knowwhatyouthink
Pleaseshareyourthoughtsonthisbookwithothersbyleavingareviewonthe
sitethatyouboughtitfrom.IfyoupurchasedthebookfromAmazon,please
leaveusanhonestreviewonthisbook'sAmazonpage.Thisisvitalsothatother
potentialreaderscanseeanduseyourunbiasedopiniontomakepurchasing
decisions,wecanunderstandwhatourcustomersthinkaboutourproducts,and
ourauthorscanseeyourfeedbackonthetitlethattheyhaveworkedwithPackt
tocreate.Itwillonlytakeafewminutesofyourtime,butisvaluabletoother
potentialcustomers,ourauthors,andPackt.Thankyou!